将字节数组转换为 BigInteger 不正确

Convert byte array to BigInteger incorrect

自从我尝试模拟我的客户端和我的服务器之间的连接处理数据包,我一直在与字节数组和 BigInteger 斗争。

当我的客户端连接到服务器时,服务器发送了一个响应,其中包含一个 RSA public 密钥,然后是一个十六进制数据包,就像这样:

C70001A100408031313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131E5AA194C3DBA71F9A38AF85D43C130A462BDCBACF85ED702B5E2FECB47197DAA433E7B5ACF5EF09A07C5DDE79B7A2EED76B91090CB981E13DBE316997FCF5DC51BE40B2D02D912457D97924B94094227F8BC65DD61FE78060B55BEAAA31C64E3B36863B407F7183DABA9D98F0C9061DED36AD16407F70C4925951BAA8807EC95

C70001A1004080后的64字节是RSApublic指数。 RSA 指数后的 128 字节是模数。

然后我尝试提取指数和模数以重新生成 RSA public 密钥。我的步骤:

if (socket == null) socket = new Socket(loginHost, loginPort);
if (is == null) is = socket.getInputStream();
if (os == null) os = socket.getOutputStream();

int packageLength = 199;

byte[] response = IOUtils.toByteArray(is, packageLength); 

byte[] rsaPubExponentBytes = Arrays.copyOfRange(response, 7, 71);
byte[] rsaModuloBytes = Arrays.copyOfRange(response, 71, packageLength);

BigInteger rsaPubExponentNumber = new BigInteger(1, rsaPubExponentBytes);
BigInteger rsaModuloNumber = new BigInteger(1, rsaModuloBytes);

PublicKey pub = null;

try {
    RSAPublicKeySpec spec = new RSAPublicKeySpec(rsaModuloNumber, rsaPubExponentNumber);
    KeyFactory factory = KeyFactory.getInstance("RSA");
            
    pub = factory.generatePublic(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
    e.printStackTrace();
}

我终于有了一把public钥匙:

Sun RSA public key, 1024 bits
  modulus: 161275860318613570660794349609651113455378591213521007305334206887210873414776462027746458127536797075994278751416175612148660222076731030937875746805281690156725876857482174279314968596913890067397869829419019179316569148699704292044128937649866682741942026327643537075317389490824101005022833230463069842581
  public exponent: 2576402308106616697565204803576809648025446765525597158856684355852417401857269811228595453373248109634555141377011045066015451991315223244608818828620081

与来自服务器的 RSA 比较

Sun RSA public key, 1024 bits
  modulus: 105278801605955351183032861925660079240049371193037037019863031780531990060755731057393990664319512505978954889835933668032824049054190807125138251307247771506918060803959346902566451812689470408398611041302277800747341495594297701677567604614145074223483256879948522462707345079591319551080307454838134385381
  public exponent: 2576402308106616697565204803576809648025446765525597158856684355852417401857269811228595453373248109634555141377011045066015451991315223244608818828620081

那么模数是错误的。我通过将 BigInteger 转换为字节数组并用 hex

显示它来仔细检查
System.out.println(bytesToHex(rsaPubExponentNumber.toByteArray()));
System.out.println(bytesToHex(rsaModuloNumber.toByteArray()));

private String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
       sb.append(String.format("%02X ", b));
    }
    return sb.toString().replaceAll(" ", "");
}

结果是:

指数: 31313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131

模数 00E5AA194C3DBA71F9A38AF85D43C130A462BDCBACF85ED702B5E2FECB47197DAA433E7B5ACF5EF09A07C5DDE79B7A2EED76B91090CB981E13DBE316997FCF5DC51BE40B2D02D912457D97924B94094227F8BC65DD61FE78060B55BEAAA31C64E3B36863B407F7183DABA9D98F0C9061DED36AD16407F70C4925951BAA8807EC95

模数现在以 00 开头并且生成了 public 密钥,但密钥不正确。

我也试过:

BigInteger exponent = new BigInteger(exponentHexString, 16);
BigInteger modulus = new BigInteger(modulusHexString, 16);

但是,结果还是一样。我该如何解决这个问题?

让我们检查一下您的代码和数据包流结构。

您将以下二进制流编码为十六进制字符串:

C70001A100408031313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131E5AA194C3DBA71F9A38AF85D43C130A462BDCBACF85ED702B5E2FECB47197DAA433E7B5ACF5EF09A07C5DDE79B7A2EED76B91090CB981E13DBE316997FCF5DC51BE40B2D02D912457D97924B94094227F8BC65DD61FE78060B55BEAAA31C64E3B36863B407F7183DABA9D98F0C9061DED36AD16407F70C4925951BAA8807EC95

根据您的代码:

  • 从索引 7 到 71 的字节是您的 指数 字节,
  • 从索引 71 到流结尾的字节是您的 模数 字节。

IIUC,你想从上面的字节串中提取指数模数


您面临的问题源于这样一个事实,即在密码学中,数字通常是 无符号整数 ,具有协议定义的字节顺序,而 Java' s BigIntegers 是 big-endiansigned 整数。

为了将任意整数(signed/unsigned、big-endian/little-endian)转换为BigInteger,必须进行一些操作。

在您的情况下,只需 reverse the byte arrays 就足够了,如评论中所建议的:

int packageLength = 199;

byte[] response = IOUtils.toByteArray(is, packageLength); 

byte[] rsaPubExponentBytes = Arrays.copyOfRange(response, 7, 71);
byte[] rsaModuloBytes = Arrays.copyOfRange(response, 71, packageLength);

// missing step!
reverse(rsaPubExponentBytes);
reverse(rsaModuloBytes);

BigInteger rsaPubExponentNumber = new BigInteger(1, rsaPubExponentBytes);
BigInteger rsaModuloNumber = new BigInteger(1, rsaModuloBytes);
// ...
public static void reverse(byte[] arr) {
    for (int i = 0; i < arr.length / 2) {
        int tmp = arr[i]
        arr[i] = arr[arr.length - 1 - i];
        arr[arr.length - 1 - i] = tmp;
    }
}

我在处理我的一个项目时 运行 遇到了类似的问题。您可能会发现我的图书馆 SRP-6 Variables 很有帮助:

byte[] stream = new Hex("C70001A100408031313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131E5AA194C3DBA71F9A38AF85D43C130A462BDCBACF85ED702B5E2FECB47197DAA433E7B5ACF5EF09A07C5DDE79B7A2EED76B91090CB981E13DBE316997FCF5DC51BE40B2D02D912457D97924B94094227F8BC65DD61FE78060B55BEAAA31C64E3B36863B407F7183DABA9D98F0C9061DED36AD16407F70C4925951BAA8807EC95").asArray();

BigInteger exponent = 
    new SRP6CustomIntegerVariable(
        Bytes.wrapped(Arrays.copyOfRange(stream, 7, 71)),
        ByteOrder.LITTLE_ENDIAN
    ).asNonNegativeBigInteger();
BigInteger modulus = 
    new SRP6CustomIntegerVariable(
        Bytes.wrapped(Arrays.copyOfRange(stream, 71, stream.length)),
        ByteOrder.LITTLE_ENDIAN
    ).asNonNegativeBigInteger();