【问题标题】:TOTP / HOTP / HmacSHA256 with unsigned bytes key in JavaTOTP / HOTP / HmacSHA256 与 Java 中的无符号字节密钥
【发布时间】:2019-01-26 18:25:35
【问题描述】:

从以下问题我们可以看出:

Java HmacSHA256 with key

Java vs. Golang for HOTP (rfc-4226)

,当在 TOTP / HOTP / HmacSHA256 用例中使用密钥时,Java 并不能很好地发挥作用。我的分析是以下造成的麻烦:

  • String.getBytes(当然)会为字符值 > 127 的字符提供负字节值;
  • javax.crypto.Macjavax.crypto.spec.SecretKeySpec 在外部和内部都使用 byte[] 来接受和转换密钥。

我们已经获得了一些Feitian C-200 Single Button OTP devices,它们带有一个十六进制字符串秘密,由大于 127 的字节值组成。

我们已经成功地在 Ruby 中为这些令牌创建了一个 PoC,它可以完美运行。由于我们要在Keycloak 中集成这些,所以我们需要找到一个Java 解决方案。

由于我们看到的每个 TOTP / HOTP / HmacSHA256 实现都使用javax.crypto 库和byte[],我们担心我们必须重写所有使用的类,但使用int 来支持这种情况。

问:还有其他方法吗?我们如何在 Java 中的 HmacSHA256 计算中使用秘密,其中字节的值 > 127 而无需重写所有内容?


更新

我看错了方向。我的问题是密钥被表示为一个字符串(Java 中的 UTF-16),其中包含由 getBytes() 分解为 两个 字节的 Unicode 字符,然后被传递到 SecretKeySpec

在此转换上强制 StandardCharsets.ISO_8859_1 可以解决问题。

【问题讨论】:

  • Java 加密将字节视为无符号并且工作正常,几十年来一直为数十亿用户工作良好;请参阅我对您的第一个链接问题的新答案,该答案证明了这一点。

标签: java cryptography hmac one-time-password totp


【解决方案1】:

签名与未签名是一个主要与人类相关的演示问题。计算机不知道也不关心 0xFF 对你来说是 -1 还是 255。所以不,你不需要使用整数,使用byte[] 就可以了。

这并不意味着您不能破坏事物,因为某些操作基于默认的签名变量类型。例如:

byte b = (byte)255;    // b is -1 or 255, depending on how you interpret it
int i = b;      // i is -1 or 2³² instead of 255
int u = b & 0xFF; // u is 255

Java 仅对原语进行了签名(booleanchar 无法承受)似乎让很多人感到震惊。然而,Java 完全有能力执行加密操作,所以所有这些“不可能”的问题都只是用户错误。在编写安全敏感代码时,这不是您想要的。

【讨论】:

  • 写得很好。请注意,加法和减法模 256 对有符号或无符号值的工作方式完全相同。当您查看以下位时,127 + 1 = -128 和 127 + 1 = 128 是相同的:0x7F + 0x01 = 0x80。 0x80 可能意味着 -128 或 128,所以完全没有问题。这是当今每个 CPU 都使用的使用负值的 二补码 方式的优点之一。就像现在字节总是 8 位一样 :)
  • ITYM (IHYM!) 如果你忽略 charboolean,Java 只有 >>signed
  • @Kayaman 感谢您的解释和指点。我将重新审视我的用例,看看为什么我会得出这个结论并在此处跟进。
  • @ChrisBrandhorst 至少你不需要编写自己的加密框架 :)
  • @Kayaman 确实 :) 我的问题在于密钥的来源,一个包含 Unicode 字符的字符串,其转换为字节数组是不正确的。再次感谢。
【解决方案2】:

不要害怕 Java :) 我已经测试了来自不同供应商的几十个令牌,Java 一切都很好,你只需要选择正确的转换器。

从 String 获取字节作为 getBytes() 而不是使用正确的转换器是常见的问题。您从供应商处获得的文件以十六进制格式表示密钥,因此只需 google 'java hex string to byte array' 并选择适合您的解决方案。

Hex、Base32、Base64 只是一种表示形式,您可以轻松地从一种转换为另一种。

【讨论】:

    【解决方案3】:

    我遇到了完全相同的问题(几年后):我们有 飞天设备,并且必须设置他们的服务器端代码。
    没有可用的实现与它们一起工作(php 或 java 都不是)。

    解决方案:飞天设备自带十六进制种子。首先,您必须将种子解码为原始二进制文件(例如,在 PHP 中使用 hex2bin())。该数据是 TOTP/HOTP 函数的正确输入。
    java的hex2bin()版本有点棘手,在OP的问题里写的很清楚。

    (长话短说:你必须用 StandardCharsets.ISO_8859_1 解释 hex2bin 的结果,否则一些字符将被解释为 2 字节 utf-16 字符,这会导致最后的密码不同)

    String hex = "1234567890ABCDEF"; // original seed from Feitian
    
    Sring secretKey = new String(hex2bin(hex), StandardCharsets.ISO_8859_1);
    Key key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.ISO_8859_1), "RAW");
    // or without String representation:
    Key key = new SecretKeySpec(hex2bin(hex), "RAW");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-19
      • 2012-12-25
      • 1970-01-01
      • 2020-03-04
      • 1970-01-01
      • 1970-01-01
      • 2017-12-03
      • 2013-03-01
      相关资源
      最近更新 更多