0

I am trying to generate OTP, but after I tried to rewrite code from python to java, I have got different outputs. I do not understand why, because some of outputs characters are same (when I change uname or ctr).

PYTHON CODE:

from Crypto.Hash import SHA256

def get_otp(uname, ctr):

    inp = uname+str(ctr)
    binp = inp.encode('ascii')

    hash=SHA256.new()
    hash.update(binp)
    dgst=bytearray(hash.digest())

    out = ''
    for x in range(9):
       out += chr(ord('a')+int(dgst[x])%26)
       if x % 3 == 2 and x != 8:
           out += '-'

    return out

print(get_otp('78951', 501585052583))

JAVA CODE:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Main 
{
    public static void main(String[] args) throws NoSuchAlgorithmException 
    {
        System.out.println(get_otp("78951", "501585052583"));        
    }

    public static String get_otp(String uname, String otp) throws NoSuchAlgorithmException
    {
        String input = uname + otp;        
        byte[] binInput = input.getBytes(StandardCharsets.US_ASCII);

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(binInput);

        String retVal = "";

        for(int i = 0; i < 9; ++i)
        {
           retVal += ((char)(((int)'a') + Math.floorMod((int)hash[i], 26)));

            if(i % 3 == 2 && i != 8)
                retVal += '-';
        }

        return retVal;
    }
}

Thank you for help.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
fauzt
  • 71
  • 10
  • Have you checked the byte arrays to be the same? – Joey Sep 27 '18 at 20:24
  • Which byte arrays you think? binp and binInput are same, hash and dgst are not same. hash contains only positive numbers, dgst in java contains some negative numbers, but this is why I am moduling that by 26.. but some of them are different as in python – fauzt Sep 27 '18 at 20:25
  • PYTHON OUTPUT: ktg-xal-hae JAVA OUTPUT: kxk-xep-lae – fauzt Sep 27 '18 at 20:28
  • 2
    My guess would be that Java is sign extending the bytes (it doesn't have unsigned ints). Try masking with 0xff before doing the mod – Sam Mason Sep 27 '18 at 20:41

2 Answers2

2

Java bytes are signed, but Python bytes are unsigned, so converting to a signed twos' complement value first should do the trick:

        b = dgst[x]
        b = (b & 127) - (b & 128) # sign-extend                                 
        out += chr(ord('a')+(b%26))  
Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
2

in crypto bytes are conventionally unsigned; I'd therefore suggest "fixing" this inconsistency in Java land, swapping your loop for something like:

    for(int i = 0; i < 9; ++i) {
        if(i > 0 && i % 3 == 0)
            retVal += '-';

        // bitwise AND with 0xff is to undo sign extension Java
        // does by default
        retVal += (char)('a' + (hash[i] & 0xff) % 26);
    }

a lot of the original brackets and casts were redundant so I've removed them. if your implementations are only ever going to be Java and Python, it doesn't matter where you "fix" this

another crypto point; if you really are after the textual one-time-password, why not just do something like:

public static String get_otp2()
{
    final SecureRandom rng = new SecureRandom();
    String out = "";
    for (int i = 0; i < 9; i++)  {
        if(i > 0 && i % 3 == 0)
            out += '-';
        out += (char)('a' + rng.nextInt(26));
    }
    return out;
}

and save this somewhere?

Sam Mason
  • 15,216
  • 1
  • 41
  • 60