RSA:从 Python 和 PHP 中的 n 和 e 生成 public 密钥给我两个不同的 public 密钥

RSA: generate public key from n and e in Python and PHP give me two different public key

在 python 中,我从 public 键中提取模数 (n) 和 (e),如下所示:

#! /usr/bin/python3.5
# -*- coding: utf-8 -*-

import rsa

(pubkey, privkey) = rsa.newkeys(512)
dec_n = pubkey.n
dec_e = pubkey.e

在base64中,n和e的值为:

n:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIGqijUcytyQLcEVxC5gK4HDx7Y_c5aMJt9OOoWDfzcrifmZr0-8Q1i_LPE-4fuBLlaPl6EmgSN2wlbF_svHZV
e:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB

我有以下 public 密钥:

-----BEGIN RSA PUBLIC KEY-----
MEgCQQCIGqijUcytyQLcEVxC5gK4HDx7Y/c5aMJt9OOoWDfzcrifmZr0+8Q1i/LP
E+4fuBLlaPl6EmgSN2wlbF/svHZVAgMBAAE=
-----END RSA PUBLIC KEY-----

我试图在 PHP 中生成相同的 public 密钥。为此,我阅读了这篇 post:openssl: how can i get public key from modulus

所以我写了这段代码:

require_once("/var/www/phpseclib/Crypt/RSA.php");

$n = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIGqijUcytyQLcEVxC5gK4HDx7Y_c5aMJt9OOoWDfzcrifmZr0-8Q1i_LPE-4fuBLlaPl6EmgSN2wlbF_svHZV";
$e = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB";

$rsa = new Crypt_RSA();

$modulus = new Math_BigInteger(base64_decode(urldecode($n)), 256);
$exponent = new Math_BigInteger(base64_decode(urldecode($e)), 256);

$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$rsa->setPublicKey();

$pub_key = $rsa->getPublicKey();
print_r($pub_key);

但我得到了这个 public 密钥:

-----BEGIN PUBLIC KEY-----
MFgwDQYJKoZIhvcNAQEBBQADRwAwRAI9AIgaqKNRzK3JAtwRXELmArgcPHthzlowm3046hYN/NyuJ+ZmvTxDWIs8Th+4EuVo+XoSaBI3bCVsWy8dlQIDAQAB
-----END PUBLIC KEY-----

差异是由两个因素造成的:首先,public键显示在PKCS1格式([1] and [2]), and in the PHP-code in the X.509-format ([1] and [3])的Python代码中。其次,Base64编码有一个bug。

  • Base64 编码:在 Python 编码中使用了 Base64url 编码,在 PHP-code 仅标准 Base64 编码 ([4])。虽然没有贴出Base64url编码的代码,但是从编码数据中出现的字符-_可以得出结论。要在 PHP 代码中使用 Base64url 解码(而不是 Base64 解码):

      $modulus = new Math_BigInteger(base64_decode(urldecode($n)), 256);
    

    必须替换为:

      $modulus = new Math_BigInteger(base64url_decode(urldecode($n)), 256);
    

    与 ([5]):

      function base64url_decode( $data ){
          return base64_decode( strtr( $data, '-_', '+/') . str_repeat('=', 3 - ( 3 + strlen( $data )) % 4 ));
      }
    

    指数也类似。

    PHP-代码因此 returns 以下 public 键:

      -----BEGIN PUBLIC KEY-----
      MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIgaqKNRzK3JAtwRXELmArgcPHtj9zlo
      wm3046hYN/NyuJ+ZmvT7xDWL8s8T7h+4EuVo+XoSaBI3bCVsX+y8dlUCAwEAAQ==
      -----END PUBLIC KEY-----
    

    注:模数和指数的Base64url-解码为十六进制:

      modulus : 0000000000000000000000000000000000000000000000000000000000000000881aa8a351ccadc902dc115c42e602b81c3c7b63f73968c26df4e3a85837f372b89f999af4fbc4358bf2cf13ee1fb812e568f97a126812376c256c5fecbc7655
    
      exponent: 000000000000000000000000000000000000000000010001
    

    没有必要使用许多 0 值进行填充(符号字节除外),不包含任何信息,只会增加数据量。

  • 格式:上一步的public密钥内容相同,只是格式不同(X.509)。显示这一点的最简单方法是使用 ([6]):

    在 PKCS1 格式中额外显示 public 密钥
      $pub_key = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
      print($pub_key . "\n");
    

    PKCS1 格式的 public 密钥与 Python 代码的密钥匹配。另一种可能性是在 ASN.1 编辑器中直接比较两个键,例如在线 ([7]).

  • 顺便说一下: 要在 PHP 中使用 Python 代码的 public 键-代码,没有必要通过模数和指数绕道。使用 ([6]):

    更容易实现
      $rsa = new Crypt_RSA();
    
      $keydata = "-----BEGIN RSA PUBLIC KEY-----\n
      MEgCQQCIGqijUcytyQLcEVxC5gK4HDx7Y/c5aMJt9OOoWDfzcrifmZr0+8Q1i/LP
      E+4fuBLlaPl6EmgSN2wlbF/svHZVAgMBAAE=
      \n-----END RSA PUBLIC KEY-----";
    
      $rsa->loadKey($keydata);
      $rsa->setPublicKey();
    
      $pub_key = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
      print($pub_key . "\n");
    
      $pub_key = $rsa->getPublicKey();
      print($pub_key . "\n");