如何在openssl的BIGNUM中使用负数?

时间:2014-07-19 04:55:41

标签: c++ openssl bignum

我想要以下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,但这也给了我错误的答案。

2 个答案:

答案 0 :(得分:4)

BN_set_negative功能设置负数。

afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d的否定实际上是-afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d,与-2相同的是2的否定。

ff5028d4a7ca52dd15a297d860053f49ad83e54f04ce0e19b908d728a342c519a3是一个很大的正数。

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

换句话说,通过在当前具有1个符号位和256个值位的数字上使用toByteArray函数,最终得到的字段宽度为264位。但是,如果你的负数的第一个半字节是7,而不是a,那么(根据这个文档 - 我还没有真正尝试过它)你会得到256 -bit字段宽度输出(即8028d4...,而不是ff8028d4

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

确保在脑海中清楚地记得大负值与通过模块算术对该负值获得的大正数之间的区别,例如{{1}的输出}。


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

我不知道这样的功能是否存在(BN库有相当糟糕的文档恕我直言,我从来没有听说过它在OpenSSL之外使用,特别是在C ++程序中没有使用)。我希望它不会,因为toByteArray的行为有点奇怪;并且在任何情况下,所有BN输出函数似乎都使用符号幅度格式输出,而不是以二进制补码格式输出。

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

或者您甚至可以以符号幅度格式输出(使用2^264BN_bn2hex)然后迭代反转每个半字节并修复开始!

NB。你想使用OpenSSL BN有什么特别的原因吗? There are many alternatives

答案 1 :(得分:1)

尽管这是2014年(五年多以前)以来的一个问题,但我想解决您的问题/弄清情况,这可能会对其他人有所帮助。

a)一个人的补语和两个人的补语

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

在二进制补码中,最高有效位用作符号位。之所以没有'-0',是因为在二进制补码中取一个否定值意味着先执行逻辑NOT(在C:波浪号中),然后再加一个。 例如,一个字节(以二进制补码形式)可以是三个值0xFF,0x00、0x01之一,表示-1、0和1。-0没有没有空间。如果有,例如0xFF(-1)并想要取反,则逻辑非运算将计算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的乘法如下(代码段,无错误检查):

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所说,这只是使afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d的-afd72b5835ad22ea5d68279ffac0b6527c1ab0fb31f1e646f728d75cbd3ae65d

您正在寻找您的大整数的二元补数

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;
}

如果'value'是您的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步的输出)字节的最高有效位已置1,则在字节0x00之前添加。如果结果为负,并且结果原始字节的最高有效位被清除,则在字节前面添加0xFF。