Flutter (Dart) 中的 RSA 加密

RSA encryption in Flutter (Dart)

我有下面的代码在 node.js 中工作,我正在尝试将其转换为直接从我的 flutter 应用程序进行 API 调用...但是我遇到了 RSA 加密问题

import fetch from "node-fetch";
import nodeRSA from "node-rsa";

const KEYVER = '23'
const ID = '123456789123456789'
const PRIVATE_KEY = "vvkmlkmmvcmemmcmdmdmm.......cddncndndncn ="

generateRequestHeader(){
const hashString = `${ID}\n{Date.now().toString()}\n{KEYVER}\n`;
const signer = new nodeRSA(PRIVATE_KEY, "pkcs1");
const signature = signer.sign(hasString);
const sign_enc = signature.toString("base64");

return {
    "AUTH_SIGNATURE": sign_enc,
    "TIMESTAMP": Date.now().toString(),
    "ID": ID,
    "KEY_VERSION":KEYVER
  };
}

async function callAPI(){
  const options = {
     method: 'GET',
     headers: generateRequestHeader()
 };

 const response = await fetch(url, options);
 return response;

}

身份验证在 node 中工作正常,但我似乎无法找到一个包来在 flutter 中复制它。我被推荐 fast_rsapackage :

#fast_rsa: ^3.4.6
import 'package:fast_rsa/fast_rsa.dart';

 class Signature{
   String Id = 'c93e7094-327b-4ff3-bf2e-c52f29a8277f';
   String privateKey = "ABCDEG....Z=";
   String keyVer = '23.0';

   generateRequestHeaders() async {
      String timeStamp = DateTime.now().toString();
      String hashString = "${Id}\n${timeStamp}\n${keyVer}\n";


     var signer = await RSA.convertPrivateKeyToPKCS1(privateKey);
     var signature = await RSA.signPKCS1v15(signer, Hash.SHA256, privateKey);
     var signature_enc = await RSA.base64(signature);

     return {

         "AUTH_SIGNATURE": signature_enc,
         "TIMESTAMP": timeStamp,
         "ID": Id,
         "KEY_VERSION": keyVer,
    };
 }

 Future<dynamic> rsaRequest() async {
    var options = {'method': 'GET', 'headers': generateRequestHeaders()};

   String url = 'https://api.........';
   http.Response response = await http.get(url, headers: options);

   try {
     if (response.statusCode == 200) {
       print(response.body);
       var document = parse(response.body);
       return document;
    } else {
      return "failed";
    }
  } catch (exp) {
    print(exp);
    return "failed";
  }
 }

}

但服务器不断返回 auth_error。任何人都可以帮助我,或者告诉我如何直接在 flutter 中使用 .js 函数。 谢谢

可以使用https://pub.dev/packages/encrypt包在dart和flutter中进行RSA加解密

import 'dart:io';
import 'package:encrypt/encrypt.dart';
import 'package:pointycastle/asymmetric/api.dart';

void main() {
  final publicKey = await parseKeyFromFile<RSAPublicKey>('test/public.pem');
  final privKey = await parseKeyFromFile<RSAPrivateKey>('test/private.pem');

  final plainText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit';
  final encrypter = Encrypter(RSA(publicKey: publicKey, privateKey: privKey));

  final encrypted = encrypter.encrypt(plainText);
  final decrypted = encrypter.decrypt(encrypted);

  print(decrypted); // Lorem ipsum dolor sit amet, consectetur adipiscing elit
  print(encrypted.base64); // kO9EbgbrSwiq0EYz0aBdljHSC/rci2854Qa+nugbhKjidlezNplsEqOxR+pr1RtICZGAtv0YGevJBaRaHS17eHuj7GXo1CM3PR6pjGxrorcwR5Q7/bVEePESsimMbhHWF+AkDIX4v0CwKx9lgaTBgC8/yJKiLmQkyDCj64J3JSE=
}

我专注于签名部分。 NodeJS 代码使用 RSA 创建签名。对于填充和摘要,应用 node-rsa 默认值:PKCS#1v1.5 填充和 SHA256,s。 here。私钥作为 DER 编码的 PKCS#1 密钥(Base64 编码)导入。签名是 Base64 编码的。

请注意,在问题中发布的 NodeJS 代码中,关于 hashString 的第二个和第三个变量的 $ 符号丢失,这可能是一个 copy/paste 错误。这必须修复,否则签名会不同!

在 Dart 方面,需要进行以下修复:

  • PKCS#1 密钥将直接传递给RSA.signPKCS1v15(),即RSA.convertPrivateKeyToPKCS1()调用将被删除。 RSA.signPKCS1v15() 需要一个 PEM 编码的密钥,即要添加页眉和页脚,在 Base64 编码的正文中,每 64 个字符后有一个换行符。
  • 要将时间戳转换为 NodeJS 代码中使用的格式:DateTime.now().millisecondsSinceEpoch.toString().
  • RSA.signPKCS1v15() returns 签名已经 base64 编码,即必须删除 RSA.base64() 调用。

修复上述问题的 fast_rsa 库的 dart 可能对应物是:

Future<Map<String,String>> generateRequestHeaders() async {
    String privateKey = '''-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----''';
    String  keyVer = "23";
    String  Id = "123456789123456789";
    String timeStamp = DateTime.now().millisecondsSinceEpoch.toString(); // "1649917884089" for testing
    String hashString = "${Id}\n${timeStamp}\n${keyVer}\n";
    String signature = await RSA.signPKCS1v15(hashString, Hash.SHA256, privateKey);
    return {
        "AUTH_SIGNATURE": signature,
        "TIMESTAMP": timeStamp,
        "ID": Id,
        "KEY_VERSION": keyVer,
    };
}
...
var result = await generateRequestHeaders();
print(result["AUTH_SIGNATURE"]); // nRuX6eY+66Ca2ZbB/ZK6ealRdS8gYJ4UKNwUOdJySqujGnwpflE8aZ45L4PfQK3qAMJh02o0SVG8uy2Mz+BFpg== for datetime = '1649917884089'

测试:
由于使用 PKCS#1 v1.5 签名是确定性的,因此 same 输入数据提供 same 签名。这使得检查两个代码的功能等效性变得容易。如果两个代码中使用相同的时间戳(例如注释掉的1649917884089),则两个代码return相同的签名(nRuX6eY+66Ca2ZbB/ZK6ealRdS8gYJ4UKNwUOdJySqujGnwpflE8aZ45L4PfQK3qAMJh02o0SVG8uy2Mz+BFpg==),证明两个代码是等价的[=23] =]

这是用于测试的固定NodeJS代码。它与问题中发布的 NodeJS 代码基本相同:

// DER encoded PKCS#1 key, Base64 encoded
// Note: For testing purposes, a 512 bits key is used. In practice, key sizes >= 2048 bits must be applied for security reasons!
const PRIVATE_KEY = "MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQTHIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssPFNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211qSIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybjBAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAfWWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ=="
const KEYVER = '23';
const ID = '123456789123456789';
const timeStamp = Date.now().toString(); // '1649917884089' for testing

function generateRequestHeader(){
    const hashString = `${ID}\n${timeStamp}\n${KEYVER}\n`; // Fix: Add the $ sign
    const signer = new nodeRSA(PRIVATE_KEY, "pkcs1");
    const signature = signer.sign(hashString); // default signing scheme: PKCS#1 v1.5 with SHA256
    const sign_enc = signature.toString("base64");    
    return {
        "AUTH_SIGNATURE": sign_enc,
        "TIMESTAMP": Date.now().toString(),
        "ID": ID,
        "KEY_VERSION":KEYVER
    };
}
...
var result = generateRequestHeader();
console.log(result.AUTH_SIGNATURE); // nRuX6eY+66Ca2ZbB/ZK6ealRdS8gYJ4UKNwUOdJySqujGnwpflE8aZ45L4PfQK3qAMJh02o0SVG8uy2Mz+BFpg== for datetime = '1649917884089'