首页 > 解决方案 > RSA:从 Python 中的 n 和 e 生成公钥,PHP 给我两个不同的公钥

问题描述

在 python 中,我从这样的公钥中提取模数 (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

我有以下公钥:

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

我试图在 PHP 中生成相同的公钥。为此,我阅读了这篇文章:openssl:我如何从模数中获取公钥

所以我写了这段代码:

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

但我得到了这个公钥:

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

标签: phppythonrsaphpseclib

解决方案


差异是由两个因素造成的:首先,公钥在 Python 代码中以 PKCS1 格式([1][2])显示,在 PHP 代码中以 X.509 格式([ 1][3])。其次,Base64 编码中存在一个错误。

  • Base64 编码:在 Python 代码中使用了 Base64 url​​ 编码,而在 PHP 代码中仅使用了标准 Base64 编码([4])。虽然没有贴出带有 Base64url 编码的代码,但这可以从字符中推断出来-_出现在编码数据中。要在 PHP 代码中使用 Base64url-decoding(而不是 Base64-decoding):

      $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 代码返回以下公钥:

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

    注意:模数和指数的 Base64url 解码是十六进制的:

      modulus : 0000000000000000000000000000000000000000000000000000000000000000881aa8a351ccadc902dc115c42e602b81c3c7b63f73968c26df4e3a85837f372b89f999af4fbc4358bf2cf13ee1fb812e568f97a126812376c256c5fecbc7655
    
      exponent: 000000000000000000000000000000000000000000010001
    

    0不需要填充许多- 值(除了符号字节),不包含任何信息,只会增加数据量。

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

      $pub_key = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
      print($pub_key . "\n");
    

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

  • 顺便说一句:要在 PHP 代码中也使用 Python 代码的公钥,没有必要通过模数和指数绕道。使用 ( [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");
    

推荐阅读