I have the code below working in node.js and I am trying to convert it to make the API call directly from my flutter app... but I am having problems with the RSA encryption
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;
}
The authentication works fine in node but I can't seem to find a package to replicate it in flutter. I was recommended 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";
}
}
}
but the server keeps returning auth_error. Can anyone help me please or show me a way to use the .js function directly inside flutter. Thanks.
CodePudding user response:
you can use https://pub.dev/packages/encrypt package to perform RSA
encryption and decryption in dart and flutter.
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=
}
CodePudding user response:
I focus on the signing part. The NodeJS code creates a signature using RSA. For padding and digest the node-rsa default values are applied: PKCS#1v1.5 padding and SHA256, s. here. The private key is imported as DER encoded PKCS#1 key (Base64 encoded). The signature is Base64 encoded.
Note that in the NodeJS code posted in the question, the $
signs for the 2nd and 3rd variables regarding hashString
are missing, which is probably a copy/paste error. This must be fixed, otherwise the signatures will differ!
On the Dart side, the following fixes are needed:
- The PKCS#1 key is to be passed directly to
RSA.signPKCS1v15()
, i.e. theRSA.convertPrivateKeyToPKCS1()
call is to be removed.RSA.signPKCS1v15()
expects a PEM encoded key, i.e. header and footer are to be added and in the Base64 encoded body there is a line break after every 64 characters. - The timestamp is to be converted to the format used in the NodeJS code:
DateTime.now().millisecondsSinceEpoch.toString()
. RSA.signPKCS1v15()
returns the signature already base64 encoded, i.e. theRSA.base64()
call must be removed.
A possible dart counterpart with the fast_rsa library that fixes the above issues is:
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'
Test:
Since signing with PKCS#1 v1.5 is deterministic, the same input data provides the same signature. This makes it easy to check the functional equivalence of both codes. If the same timestamp is used in both codes (e.g. the commented out 1649917884089
), both codes return the same signature (nRuX6eY 66Ca2ZbB/ZK6ealRdS8gYJ4UKNwUOdJySqujGnwpflE8aZ45L4PfQK3qAMJh02o0SVG8uy2Mz BFpg==
), which proves the equivalence of both codes.
This is the fixed NodeJS code used for the test. It is essentially the same as the NodeJS code posted in the question:
// 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'