【问题标题】:How to use negative number with openssl's BIGNUM?如何在 openssl 的 BIGNUM 中使用负数?
【发布时间】:2014-07-19 04:55:41
【问题描述】:

我想要以下 Java 代码的 C++ 版本。

BigInteger x = new BigInteger("00afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d", 16);
BigInteger y = x.multiply(BigInteger.valueOf(-1));

//prints y = ff5028d4a7ca52dd15a297d860053f49ad83e54f04ce0e19b908d728a342c519a3
System.out.println("y = " + new String(Hex.encode(y.toByteArray())));

这是我对解决方案的尝试。

BIGNUM* x = BN_new();
BN_CTX* ctx = BN_CTX_new();
std::vector<unsigned char> xBytes = hexStringToBytes(“00afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d");
BN_bin2bn(&xBytes[0], xBytes.size(), x);

BIGNUM* negative1 = BN_new();
std::vector<unsigned char> negative1Bytes = hexStringToBytes("ff");
BN_bin2bn(&negative1Bytes[0], negative1Bytes.size(), negative1);

BIGNUM* y = BN_new();
BN_mul(y, x, negative1, ctx);

char* yHex = BN_bn2hex(y);
std::string yStr(yHex);
//prints y = AF27542CDD7775C7730ABF785AC5F59C299E964A36BFF460B031AE85607DAB76A3
std::cout <<"y = " << yStr << std::endl;

(忽略此案。)我做错了什么?如何让我的 C++ 代码输出正确的值“ff5028d4a7ca52dd15a297d860053f49ad83e54f04ce0e19b908d728a342c519a3”。我也尝试通过执行 BN_set_word(negative1, -1) 来设置negative1,但这也给了我错误的答案。

【问题讨论】:

  • hexStringToBytes(“00 中有一个奇怪的引号 ...希望这是一个复制粘贴错误,而不是实际上在您的源文件中
  • 你试过BN_set_negative吗?
  • 嗯。这似乎也不起作用。只会得到“-AFD72B5835AD22EA5D68279FFAC0B6527C1AB0FB31F1E646F728D75CBD3AE65D”
  • 以及 afd 的负面... -afd...
  • 也许添加标签“java” - 我通过阅读 BigInteger 的在线文档了解您的要求;可能我误读了在 Java 方面更有经验的人可能能够澄清的文档

标签: c++ openssl bignum


【解决方案1】:

BN_set_negative 函数设置一个负数。

afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d 的负数实际上是 -afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d ,就像-22 的负数一样。

ff5028d4a7ca52dd15a297d860053f49ad83e54f04ce0e19b908d728a342c519a3 是一个很大的正数。

您在 Java 中看到这个数字的原因是由于 toByteArray 调用。 According to its documentation,它选择的最小字段宽度是整数字节,并且还能够保存负数的二进制补码表示。

换句话说,通过对当前具有 1 个符号位和 256 个值位的数字使用 toByteArray 函数,您最终会得到 264 位的字段宽度。但是,如果您的负数的第一个半字节是 7,而不是 a,那么(根据此文档 - 我实际上没有尝试过)您将获得 256 位字段宽度(即 8028d4... ,而不是ff8028d4

您在代码中使用的前导 00 在 OpenSSL BN 中无关紧要。我不确定它在 BigInteger 中是否重要,尽管该构造函数的文档说“字符串表示由一个可选的减号或加号组成,后跟指定基数中的一个或多个数字序列。”;因此它接受减号这一事实表明,如果不存在减号,则即使设置了 MSB,输入也会被视为一个大的正数。 (希望 Java 程序员可以帮我弄清楚这一段)。

确保您清楚区分大的负值和通过对该负值的模运算获得的大的正数,例如toByteArray 的输出。


所以你的问题真的是:Openssl BN 有模拟 BigInteger.toByteArray() 行为的函数吗?

我不知道这样的函数是否存在(BN 库的文档非常糟糕,恕我直言,我从未听说过它在 OpenSSL 之外使用,尤其是在 C++ 程序中没有使用)。我希望它不会,因为toByteArray 的行为有点奇怪;在任何情况下,所有 BN 输出函数似乎都使用符号大小格式输出,而不是二进制补码格式。

但是要复制该输出,您可以将2^2562^264 添加到大的负数,然后执行BN_bn2hex。在这种特殊情况下,添加2^264,一般情况下,您必须测量正在存储的数字的当前位长,并将指数四舍五入到最接近的 8 倍数。

或者您甚至可以以符号幅度格式输出(使用BN_bn2hexBN_bn2mpi),然后通过反转每个半字节进行迭代并修复开始!

注意。你有什么特别的原因要使用 OpenSSL BN 吗? There are many alternatives.

【讨论】:

  • BN_bn2mpi 以符号幅度格式输出(它只是设置开始,然后调用 BN_bn2bin)
  • 文档很糟糕,我不得不查看源代码以了解实际发生的情况
【解决方案2】:

虽然这是 2014 年(五年多前)的问题,但我想解决您的问题/澄清情况,这可能对其他人有所帮助。

a) 一个补码和二进制补码

在有限数论中,数字有“一个补码”和“二进制补码”表示。一个补码仅存储绝对(正)值并且不知道符号。如果您想为存储为补码的数字添加符号,则必须单独存储它,例如一位(0=正,1=负)。这正是浮点数(IEEE 754)的情况。尾数与指数和一个附加符号位一起作为反码存储。补码中的数字有两个零:-0 和 +0,因为您将符号独立于绝对值本身。

在二进制补码中,最高有效位用作符号位。没有“-0”,因为取反二进制补码中的值意味着执行逻辑非(在 C 中:波浪号)运算,然后加一。 例如,一个字节(二进制补码)可以是 0xFF、0x00、0x01 三个值之一,分别表示 -1、0 和 1。-0 没有空间。如果你有,例如0xFF (-1) 并且想要否定它,然后逻辑 NOT 操作计算 0xFF => 0x00。加一得到 0x01,即 1。

b) OpenSSL BIGNUM 和 Java BigInteger

OpenSSL 的 BIGNUM 实现将数字表示为补码。 Java BigInteger 将数字视为二进制补码。那是你的灾难。你的大整数(十六进制)是 00afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d。这是一个 256 位正整数。它由 33 个字节组成,因为有一个前导零字节 0x00,这对于存储为二进制补码的整数是绝对正确的,因为设置了最高有效位(省略初始 0x00)(在 0xAF 中),这将使这个数字成为负数号码。

c) 您正在寻找的解决方案

OpenSSL 的函数 bin2bn 仅适用于绝对值。对于 OpenSSL,您可以保留初始零字节或将其切断 - 没有任何区别,因为 OpenSSL 无论如何都会规范化输入数据,这意味着切断所有前导零字节。你的代码的下一个问题是你想让这个整数为负的方式:你想把它乘以-1。使用 0xFF 作为 bin2bn 的唯一输入字节使得这个 255,而不是 -1。事实上,你将你的大整数乘以 255,得到总体结果 AF27542CDD7775C7730ABF785AC5F59C299E964A36BFF460B031AE85607DAB76A3,它仍然是正数。

与 -1 的乘法如下(sn-p,无错误检查):

BIGNUM* x = BN_bin2bn(&xBytes[0], (int)xBytes.size(), NULL);
BIGNUM* negative1 = BN_new();
BN_one(negative1); /* negative1 is +1 */
BN_set_negative(negative1, 1); /* negative1 is now -1 */
BN_CTX* ctx = BN_CTX_new();
BIGNUM* y = BN_new();
BN_mul(y, x, negative1, ctx);

更简单的是:

BIGNUM* x = BN_bin2bn(&xBytes[0], (int)xBytes.size(), NULL);
BN_set_negative(x,1);

这并不能解决您的问题,因为正如 M.M 所说,这只会使 -afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d 从 afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f7287e

你正在寻找你的大整数的二进制补码,即

int i;

for (i = 0; i < (int)sizeof(value); i++)
  value[i] = ~value[i];

for (i = ((int)sizeof(posvalue)) - 1; i >= 0; i--)
{
  value[i]++;
  if (0x00 != value[i])
    break;
}

如果“值”是您的 33 字节输入数组,其中包含以字节 0x00 为前缀的大整数,则这是二进制补码的未优化版本。此操作的结果是 33 个字节 ff5028d4a7ca52dd15a297d860053f49ad83e54f04ce0e19b908d728a342c519a3。

d) 使用二进制补码和 OpenSSL BIGNUM

整个序列是这样的:

  1. 序言:如果输入为负数(检查最高有效位),则计算输入的二进制补码。
  2. 使用 BN_bin2bn 转换为 BIGNUM
  3. 如果输入为负数,则调用 BN_set_negative(x,1)
  4. 主要功能:使用 OpenSSL BIGNUM 包进行所有算术运算
  5. 调用 BN_is_negative 检查否定结果
  6. 使用 BN_bn2bin 转换为原始二进制字节
  7. 如果结果为负,则计算结果的二进制补码。
  8. 结语:如果结果为正且结果原始(步骤 7 的输出)字节的最高有效位已设置,则在前面添加一个字节 0x00。如果结果为负且结果原始字节的最高有效位已清除,则在前面添加一个字节 0xFF。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 1970-01-01
    相关资源
    最近更新 更多