简体   繁体   中英

Java SHA256 generates different hash as in Python

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.

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))  

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM