Java与Python HMAC-SHA256不匹配 - java

根据最近对该问题的反馈和发现,我重写了该问题以消除噪音。

我有2个独立的代码路径,一个是Java(Android),一个是Python,它们通过协商Android设备和Python / Django之间的配对来实现以下目的。

Java:

  • 生成syncKey
  • 使用presharedKey(包括syncKey)
  • 散列各种值的串联字符串

  • 使用presharedKey
  • 加密syncKey

  • 将哈希,加密的syncKey,DeviceId和任意变量发送到Web服务器
  • python

  • 从deviceId
  • 获取presharedKey

  • 解密加密的syncKey
  • 使用presharedKey(包括解密的syncKey)哈希各种值的串联字符串
  • 确保哈希匹配,这确认syncKey已成功解密,并且deviceId持有正确的presharedKey。
  • 现在,如果我发送未加密的syncKey,则此过程有效。最终的哈希匹配,这证明deviceId具有正确的预共享密钥,但是,尽管将syncKey和串联的字符串看起来都完美匹配,但是一旦我将en / decryption添加到进程中,哈希就不再匹配。 Java / Python的调试输出中的字符。

    该过程的一个怪癖是AES256加密算法需要256位密钥,因此我将512位presharedKey切成两半。全面使用256位密钥的另一种方法是要求我将密钥通过python端的encode('ascii')传递,否则它将在使用较短的密钥进行哈希处理时引发错误。

    以下是相关代码:

    Java:

    String presharedKey = getKey();
    // f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d
    
    String deviceId = getDeviceId();
    // 1605788742789230
    
    SyncKey syncKey = generateSyncKey();
    // 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
    
    String concat = syncKey.hexString();
    // 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
    
    String ALGORITHM = "HmacSHA256";
    String hash = null;
    try {
        SecretKeySpec keySpec = new SecretKeySpec(
            presharedKey.getBytes(),
            ALGORITHM);
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(keySpec);
        byte[] result = mac.doFinal(concat.getBytes());
        hash = Base64.encodeToString(result, Base64.DEFAULT);
        // FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs=
    } catch (NoSuchAlgorithmException x) {
    } catch (InvalidKeyException x) {
    }
    
    String encKey = presharedKey.substring(0, presharedKey.length() / 2);
    // f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd
    
    int len = encKey.length();
    byte[] encKeyBytes = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        encKeyBytes[i / 2] = (byte) ((Character.digit(encKey.charAt(i), 16) << 4)
                + Character.digit(encKey.charAt(i+1), 16));
    }
    
    String encryptedSyncKey = null;
    try {
        byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKeySpec encKeySpec = new SecretKeySpec(encKeyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, encKeySpec, ivSpec);
        byte[] encryptedSyncKeyBytes = cipher.doFinal(syncKey.hexString().getBytes());
        encryptedSyncKey = Base64.encodeToString(encryptedSyncKeyBytes, Base64.DEFAULT);
        /*
            Yrl0/SuTUUTC6oJ8o4TCOy65EwO0JzoXfEi9kLq0AOlf6rH+nN7+BEc0s5uE7TIo1UlJb/DvR2Ca
            ACmQVXXhgpZUTB4sQ0eSo+t32lg0EEb9xKI5CZ4l9QO5raw0xBn7r/tfIdVm8AIFkN9QCcthS0DF
            KH3oWhpwNS+tfEuibLPgGqP/zGTozmido9U9lb4n
        */
    } catch (InvalidAlgorithmParameterException e) {
    } catch (NoSuchAlgorithmException e) {
    } catch (NoSuchPaddingException e) {
    } catch (InvalidKeyException e) {
    } catch (IllegalBlockSizeException e) {
    } catch (BadPaddingException e) {
    }
    
    sendStuffToWeb(encryptedSyncKey, deviceId, hash);
    

    python :

    hash = getHash(request)
    # hash from Java: FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs=
    
    encrypted_sync_key = getEncSyncKey(request)
    # encryptedSyncKey from Java:
    # Yrl0/SuTUUTC6oJ8o4TCOy65EwO0JzoXfEi9kLq0AOlf6rH+nN7+BEc0s5uE7TIo1UlJb/DvR2Ca
    # ACmQVXXhgpZUTB4sQ0eSo+t32lg0EEb9xKI5CZ4l9QO5raw0xBn7r/tfIdVm8AIFkN9QCcthS0DF
    # KH3oWhpwNS+tfEuibLPgGqP/zGTozmido9U9lb4n
    
    device_id = getDeviceId(request)
    # 1605788742789230
    
    preshared_key = getPresharedKeyFromDevice(deviceId)
    # f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d
    
    enc_key = preshared_key[:len(preshared_key)/2]
    # f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd
    
    aes = AES.new(enc_key.decode('hex'), AES.MODE_CBC, IV="\x00"*16)
    sync_key = aes.decrypt(base64.b64decode(encrypted_sync_key))
    # 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
    
    concat = sync_key
    # 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
    
    import hashlib
    from hmac import new as hmac
    
    verify_hash = hmac(preshared_key, concat, hashlib.sha256).digest().encode('base64')
    # IoSc2w2sQ4/fwhJTdUQHw/Hdyjy+ranzQ1z3J5LfYbA=
    

    从下面的调试输出中,您可以看到syncKey已成功加密和解密,并且concat是相同的。但是,生成的hash最终会有所不同。

    参考方案

    您的Python代码错误。我可以用Python复制您在Java中得到的答案。

    如果我使用您的输入:

    >>> preshared_key_hex
    b'f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d'
    >>> concat_hex
    b'824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9'
    

    我得到了与Java相同的价值:

    >>> base64.b64encode(hmac.new(preshared_key_hex, concat_hex, hashlib.sha256).digest())
    b'FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs='
    

    但是,该值也可能是错误的。您几乎应该肯定会十六进制解码输入值。

    我无法重现您在Python中获得的东西;您传递给hmac.new的值之一不是您认为的那样。在调用print之前立即对其进行hmac.new编码,您应该看到不匹配的内容。

    如何使用PEM私钥通过HMAC在PHP中签名数据 - java

    我看过很多关于如何在PHP中创建HMAC的文章,但是使用的机密始终是一个简单的字符串(例如“ secret”)。我需要做的是创建一个HMAC,其秘密是私钥,例如从包含PEM格式私钥的文件中加载。我一直无法找到有关是否可以在PHP中(使用hash_hmac)的任何信息。用法在Java中创建了使用私钥的HmacSHA256签名,我想在PHP中重新创建此签名(根据…

    Java:线程池如何将线程映射到可运行对象 - java

    试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

    JAVA:字节码和二进制有什么区别? - java

    java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

    java:继承 - java

    有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

    Java:BigInteger,如何通过OutputStream编写它 - java

    我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…