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

2020-07-21 13:14:44 +08:00
 sudoy

对方提供的写法是 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;
  }
}
1141 次点击
所在节点    问与答
16 条回复
sudoy
2020-07-21 13:27:23 +08:00
如果写出成品,请附上您的打赏码,我会直接打赏,谢谢!
SakuraSa
2020-07-21 15:56:50 +08:00
先安装 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
2020-07-21 16:06:04 +08:00
java 和 python 涉及到 pkcs8 和 pkcs1 的问题,挺恶心。
sudoy
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
2020-07-21 16:23:03 +08:00
sudoy
2020-07-21 16:24:10 +08:00
@Vegetable 这些数字签名方法大都是一些比较老的公司用的,确实很恶心,很多相关的库要么没人维护,要么需要装各种依赖
Vegetable
2020-07-21 16:25:02 +08:00
我上次做这个太久了记不清了,参考 @SakuraSa 的代码,写了一个,等我发个 GIST
rimutuyuan
2020-07-21 16:28:28 +08:00
可以使用 shell 吗
openssl rsa -in pkcs8.pem -out pkcs1.pem
Vegetable
2020-07-21 16:34:10 +08:00
你 DEMO 代码里的证书是单行的 PKCS8,我不确定现在 Py 能不能直接用,所以我给转成 1 的了,同时 Java 代码的 base64 库我也改成了 import java.util.Base64;才能在我的电脑上跑起来。

https://gist.github.com/luliangce/da56d70d0b206eeaa77061877031d8cf
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
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
2020-07-21 17:51:59 +08:00
@Vegetable 非常感谢,我刚才试了一下直接用单行的 PKCS8,签名成功,但是拿这个签名去请求 API 的时候提示 401 错误,也就是验证失败,正在查找原因。
sudoy
2020-07-21 18:41:02 +08:00
@Vegetable 请加我 v 信,我给你发个红包,表示感谢 :)
sudoy
2020-07-21 18:44:40 +08:00
@rimutuyuan 不支持 shell 呢,不过可以将他给的 key 在 shell 那里转换好,再去代码里面调用
Vegetable
2020-07-22 00:12:46 +08:00
@sudoy #13 这就不必啦,举手之劳罢了
sudoy
2020-07-22 09:52:54 +08:00
@Vegetable 感谢👍

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/691873

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX