V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
sudoy
V2EX  ›  问与答

[求助帖] Python 生成数字签名

  •  
  •   sudoy · 2020-07-21 13:14:44 +08:00 · 1044 次点击
    这是一个创建于 1347 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对方提供的写法是 Java 和 PHP,我想把他变成 Python 的,哪位大神帮忙指点一下。

    Java 的写法

    import org.apache.commons.codec.binary.Base64;
    import java.security.KeyFactory;
    import java.security.PrivateKey;
    import java.security.Signature;
    import java.security.spec.PKCS8EncodedKeySpec;
    
    public class SHA256WithRSAAlgo {
      private static String consumerId = "b68d2a72...."; // Trimmed for security reason
      private static String baseUrl = "https://api-gateway.walmart.com/v3/feeds";
      private static String privateEncodedStr = "MIICeAIBADANBgkqhkiG9w0BAQEFAA......";       //Trimmed for security reasons
      public static void main(String[] args) {
        String httpMethod = "GET";
        String timestamp = String.valueOf(System.currentTimeMillis());
        String stringToSign = consumerId + "\n" + baseUrl + "\n" + httpMethod + "\n" + timestamp + "\n";
        String signedString = SHA256WithRSAAlgo.signData(stringToSign, privateEncodedStr);
        System.out.println("Signed String: " + signedString);
      }
      public static String signData(String stringToBeSigned, String encodedPrivateKey) {
        String signatureString = null;
        try {
          byte[] encodedKeyBytes = Base64.decodeBase64(encodedPrivateKey);
          PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encodedKeyBytes);
          KeyFactory kf = KeyFactory.getInstance("RSA");
          PrivateKey myPrivateKey = kf.generatePrivate(privSpec);
          Signature signature = Signature.getInstance("SHA256withRSA");
          signature.initSign(myPrivateKey);
          byte[] data = stringToBeSigned.getBytes("UTF-8");
          signature.update(data);
          byte[] signedBytes = signature.sign();
          signatureString = Base64.encodeBase64String(signedBytes);
        } catch (Exception e) {
          e.printStackTrace();
        }
        return signatureString;
      }
    }
    

    PHP 的写法

    $URL = //Walmart API URL along with path and query parameters
    $RequestMethod = //Request method type i.e GET, POST
    $Timestamp = round(microtime(true) * 1000); //Current system timestamp
    function _GetWalmartAuthSignature($URL, $RequestMethod, $Timestamp) {
      $WalmartPrivateKey = //Your Walmart Private Key;
      $WalmartConsumerID = //Your Walmart Comsumer Id;
      // CONSTRUCT THE AUTH DATA WE WANT TO SIGN
      $AuthData = $WalmartConsumerID."\n";
      $AuthData .= $URL."\n";
      $AuthData .= $RequestMethod."\n";
      $AuthData .= $Timestamp."\n";
      // GET AN OPENSSL USABLE PRIVATE KEY FROMM THE WARMART SUPPLIED SECRET
      $Pem = _ConvertPkcs8ToPem(base64_decode($WalmartPrivateKey));
      $PrivateKey = openssl_pkey_get_private($Pem);
      // SIGN THE DATA. USE sha256 HASH
      $Hash = defined("OPENSSL_ALGO_SHA256") ? OPENSSL_ALGO_SHA256 : "sha256";
      if (!openssl_sign($AuthData, $Signature, $PrivateKey, $Hash))
      { // IF ERROR RETURN NULL return null; }
        //ENCODE THE SIGNATURE AND RETURN
        return base64_encode($Signature);
      }
      function _ConvertPkcs8ToPem($der)
      {
        static $BEGIN_MARKER = "-----BEGIN PRIVATE KEY-----";
        static $END_MARKER = "-----END PRIVATE KEY-----";
        $key = base64_encode($der);
        $pem = $BEGIN_MARKER . "\n";
        $pem .= chunk_split($key, 64, "\n");
        $pem .= $END_MARKER . "\n";
        return $pem;
      }
    }
    
    第 1 条附言  ·  2020-07-21 15:30:55 +08:00
    打赏改为付费,预算 200rmb,会的请加 V
    16 条回复    2020-07-22 09:52:54 +08:00
    sudoy
        1
    sudoy  
    OP
       2020-07-21 13:27:23 +08:00
    如果写出成品,请附上您的打赏码,我会直接打赏,谢谢!
    SakuraSa
        2
    SakuraSa  
       2020-07-21 15:56:50 +08:00   ❤️ 1
    先安装 Crypto
    ```bash
    pip3 install pycryptodome
    ```

    大概是这样?
    ```python3

    import base64
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.PublicKey import RSA
    from Crypto.Hash import SHA256

    priv_key = b"MIIJRAIBADANBg..."

    def sign(key, data):
    private_key = RSA.importKey(base64.decodebytes(key))
    cipher = PKCS1_v1_5.new(private_key)
    h = SHA256.new(data)
    signature = cipher.sign(h)
    return base64.b64encode(signature)

    def main():
    print("signed:", sign(priv_key, b"hello world"))


    if __name__ == '__main__':
    main()
    ```
    Vegetable
        3
    Vegetable  
       2020-07-21 16:06:04 +08:00
    java 和 python 涉及到 pkcs8 和 pkcs1 的问题,挺恶心。
    sudoy
        4
    sudoy  
    OP
       2020-07-21 16:21:54 +08:00
    @SakuraSa 谢谢!

    根据开发文档,需要( 1 )先将平台提供的 private key 进行 base64 解密得到 pkcs8 格式的密钥,( 2 )然后将该 pkcs8 密钥转化成 PEM,( 3 )然后再通过 openssl 将这个 PEM 转换成 private key,最后( 4 )用这个 private key 去签名请求数据。

    您写的这个是第( 4 )步,前面几步搜了半天找不到合适的方法。

    我目前搜到的有帮助的相关资料:
    http://alexfu.cc/get/passage/100
    https://gist.github.com/lkdocs/6519366
    SakuraSa
        5
    SakuraSa  
       2020-07-21 16:23:03 +08:00   ❤️ 1
    sudoy
        6
    sudoy  
    OP
       2020-07-21 16:24:10 +08:00
    @Vegetable 这些数字签名方法大都是一些比较老的公司用的,确实很恶心,很多相关的库要么没人维护,要么需要装各种依赖
    Vegetable
        7
    Vegetable  
       2020-07-21 16:25:02 +08:00
    我上次做这个太久了记不清了,参考 @SakuraSa 的代码,写了一个,等我发个 GIST
    rimutuyuan
        8
    rimutuyuan  
       2020-07-21 16:28:28 +08:00
    可以使用 shell 吗
    openssl rsa -in pkcs8.pem -out pkcs1.pem
    Vegetable
        9
    Vegetable  
       2020-07-21 16:34:10 +08:00   ❤️ 2
    你 DEMO 代码里的证书是单行的 PKCS8,我不确定现在 Py 能不能直接用,所以我给转成 1 的了,同时 Java 代码的 base64 库我也改成了 import java.util.Base64;才能在我的电脑上跑起来。

    https://gist.github.com/luliangce/da56d70d0b206eeaa77061877031d8cf
    Vegetable
        10
    Vegetable  
       2020-07-21 16:35:48 +08:00
    $ python py.py
    b'jZpuuj7nfamQqxsMvkWg/eJcnO3a0/2LVmjuRmUfD2CCJgT7LU2iz3Kv0kOT/ADfUBtQ9lIHwi1WfyirtixQ1SKnASRZLpdcY/iufnvydpyqVdZ/6PYMAeYTq1451PpQ0pIR8P8UHaVoCcZoioYXl7pa9IRwDZWyIxP8CWf2xcU='
    (sign-lDeXAck8-py3.7)
    ~/Desktop/proj/sign ⌚ 16:35:06
    $ javac SHA256WithRSAAlgo.java
    (sign-lDeXAck8-py3.7)
    ~/Desktop/proj/sign ⌚ 16:35:15
    $ java SHA256WithRSAAlgo
    Signed String: jZpuuj7nfamQqxsMvkWg/eJcnO3a0/2LVmjuRmUfD2CCJgT7LU2iz3Kv0kOT/ADfUBtQ9lIHwi1WfyirtixQ1SKnASRZLpdcY/iufnvydpyqVdZ/6PYMAeYTq1451PpQ0pIR8P8UHaVoCcZoioYXl7pa9IRwDZWyIxP8CWf2xcU=
    sudoy
        11
    sudoy  
    OP
       2020-07-21 17:50:29 +08:00
    @SakuraSa 嗯,奇怪的是我直接用这个 pkcs8,提示说不支持这个类型的 RSA


    raise ValueError("RSA key format is not supported")
    ValueError: RSA key format is not supported
    sudoy
        12
    sudoy  
    OP
       2020-07-21 17:51:59 +08:00
    @Vegetable 非常感谢,我刚才试了一下直接用单行的 PKCS8,签名成功,但是拿这个签名去请求 API 的时候提示 401 错误,也就是验证失败,正在查找原因。
    sudoy
        13
    sudoy  
    OP
       2020-07-21 18:41:02 +08:00
    @Vegetable 请加我 v 信,我给你发个红包,表示感谢 :)
    sudoy
        14
    sudoy  
    OP
       2020-07-21 18:44:40 +08:00
    @rimutuyuan 不支持 shell 呢,不过可以将他给的 key 在 shell 那里转换好,再去代码里面调用
    Vegetable
        15
    Vegetable  
       2020-07-22 00:12:46 +08:00   ❤️ 1
    @sudoy #13 这就不必啦,举手之劳罢了
    sudoy
        16
    sudoy  
    OP
       2020-07-22 09:52:54 +08:00
    @Vegetable 感谢👍
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3388 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:41 · PVG 18:41 · LAX 03:41 · JFK 06:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.