如何在 Flutter 中从 Node.js 复制 RSA 身份验证?

问题描述 投票:0回答:2

我有下面的代码在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.js 中工作正常,但我似乎找不到在 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 函数?

node.js flutter dart rsa
2个回答
2
投票

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

请注意,在问题中发布的 NodeJS 代码中,关于

$
的第二个和第三个变量的
hashString
符号缺失,这可能是复制/粘贴错误。这个必须修复,否则签名会不同!

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

  • PKCS#1 密钥将被直接传递到
    RSA.signPKCS1v15()
    ,即
    RSA.convertPrivateKeyToPKCS1()
    调用将被删除。
    RSA.signPKCS1v15()
    需要 PEM 编码密钥,即添加页眉和页脚,并且在 Base64 编码正文中每 64 个字符后有一个换行符。
  • 时间戳将转换为 NodeJS 代码中使用的格式:
    DateTime.now().millisecondsSinceEpoch.toString()
  • RSA.signPKCS1v15()
    返回已进行 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
),则两个代码返回相同的签名(
nRuX6eY+66Ca2ZbB/ZK6ealRdS8gYJ4UKNwUOdJySqujGnwpflE8aZ45L4PfQK3qAMJh02o0SVG8uy2Mz+BFpg==
),这证明了两个代码的等效性。

这是用于测试的固定 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' 

2
投票

您可以使用 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=
}
© www.soinside.com 2019 - 2024. All rights reserved.