Swift encryption and NodeJS decryption producing inconsistent results - node.js

I'm not very familiar with Crypto, but i did ensure that the iv length buffer returns the correct length, encryptionKey used are the same.
The expected result from NodeJS, is a preset IV Length of 16 randombytes + encryptedText generated with aes-256-cbc combined into a hex String of length 64.
Tests encrypting in Node and decrypting it produces the expected result. But when iOS sends the payload it decrypts into an unknown string.
However, when iOS encrypts and sends the data. I'm unable to decrypt it to get the expected string.
For iOS i'm using the CommonCryto library
import CommonCrypto
struct AES {
private let key: String
init?(key: String) {
guard key.count == kCCKeySizeAES256 else {
debugPrint("Error: Failed to set a key.")
return nil
}
self.key = key
}
func encrypt(string: String) -> Data? {
return crypt(data: string.data(using: .utf8), operation: kCCEncrypt)
}
private func crypt(data: Data?, operation: Int) -> Data? {
guard let data = data else {
return nil
}
var ivBytes: [UInt8]
var inBytes: [UInt8]
var outLength: Int
if operation == kCCEncrypt {
ivBytes = [UInt8](repeating: 0, count: kCCBlockSizeAES128)
guard kCCSuccess == SecRandomCopyBytes(kSecRandomDefault, ivBytes.count, &ivBytes) else {
fatalError("IV creation failed!")
}
inBytes = Array(data)
outLength = data.count + kCCBlockSizeAES128
} else {
ivBytes = Array(Array(data).dropLast(data.count - kCCBlockSizeAES128))
inBytes = Array(Array(data).dropFirst(kCCBlockSizeAES128))
outLength = inBytes.count
}
var outBytes = [UInt8](repeating: 0, count: outLength)
var bytesMutated = 0
guard kCCSuccess == CCCrypt(CCOperation(operation), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), Array(key), key.count, &ivBytes, &inBytes, inBytes.count, &outBytes, outLength, &bytesMutated) else {
fatalError("Cryptography operation \(operation) failed")
}
var outData = Data(bytes: &outBytes, count: bytesMutated)
if operation == kCCEncrypt {
ivBytes.append(contentsOf: Array(outData))
outData = Data(ivBytes)
}
return outData
}
}
And how i decrypt in NodeJS:
const decrypt = functions.https.onCall(async (data, context) => {
const uid = context && context.auth && context.auth.uid;
if(!uid) {
return sendErrorResponse({
payload: 'Unauthorised',
statusCode: 401,
});
}
const { password } = data;
const MID = password.length / 2;
const textPart = [password.slice(0,MID),password.slice(MID)];
const iv = Buffer.from(textPart.shift(),'hex');
const encryptedText = Buffer.from(textPart.join(),'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', encryptionKey, iv).setAutoPadding(false);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
});

Look for cross plate-form AES encryption

Related

Validate mining share in NodeJS before submit to pool (Stratum)

I am trying to validate share if it meets minimum difficulty.
I have all required data to create block hash and compare with difficulty.
My code is not generating valid block hash, but don't understand why.
My code with example data:
const crypto = require('crypto');
sha256 = function(buffer){
var hash1 = crypto.createHash('sha256');
hash1.update(buffer);
return hash1.digest();
};
sha256d = function(buffer){
return sha256(sha256(buffer));
};
reverseBuffer = function(buff){
var reversed = new Buffer.alloc(buff.length);
for (var i = buff.length - 1; i >= 0; i--)
reversed[buff.length - i - 1] = buff[i];
return reversed;
};
reverseHex = function(hex){
return reverseBuffer(Buffer.from(hex, 'hex')).toString('hex');
};
serializeCoinbase = function(coinbase1, coinbase2, extraNonce1, extraNonce2){
var coinbase = coinbase1+
extraNonce1+
extraNonce2+
coinbase2;
return Buffer.from(coinbase, 'hex');
};
MerkleRootWithCoinbase = function(merkleTree,coinbaseHash){
var hash = coinbaseHash;
merkleTree.forEach(a => {
hash = sha256d(Buffer.from(hash + a,'hex')).toString('hex')
});
return hash
}
convertPreviousblockhash = function(previousblockhash){
return previousblockhash.match(/.{1,8}/g).reverse().join('')
}
blockHeader = function(blockVersion, previousblockhash, merkleRoot, nTime, nBits, nonce){
previousblockhash = convertPreviousblockhash(previousblockhash)
var hash = reverseHex(blockVersion)+
reverseHex(previousblockhash)+
reverseHex(merkleRoot)+
reverseHex(nTime)+
reverseHex(nBits)+
reverseHex(nonce);
return Buffer.from(hash, 'hex');
};
// blockTemplate is received from pool (mining.notify)
let blockTemplate = {
ExtraNonce1: '929e4bb4',
ExtraNonce2_size: 4,
previousblockhash: '83ed60ce078736fe15528d3f5ea5cfdd0ed72b04000a31440000000000000000',
coinbase: [
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5c0332410a192f5669614254432f4d696e6564206279206c6576616d75732f2cfabe6d6def4d71fab9c4856256f87621806274036feb16b79c411157a920716275e722951000000000000000108ad5ef13de0027ea',
'ffffffff04ce4c6229000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3acac6189d090598816a3e1092b966aad3c6aef15e3c826a334cea7f21002fd6540000000000000000266a24b9e11b6d65ba1a6db71ac060e04c3cb5bf049af5478970ded266f93b2f5b89f86348c9fc0000000000000000266a24aa21a9edf2716abb1dd5ac06ad1fed7f6bde943fc1657a0fd53a1c11823a4ffb5cc823e400000000'
],
merkleTree: [
'628a4d82f4950e0d55407f231057e8524a27e724143667cfd6c4bb3bc323d75f',
'e1ec3b078e79bd6da16899697e47a1eff05f27304baf7bf4fb3784145b452343',
'dfac6e968c29d40b35f34474b081b3fb9a054497c8ce8cebfca36471e9b5f912',
'35ea339bda0105cd0401e5f4dd89c511a41ea34117d839d6d3e32b575f4e4dc0',
'97bd6bfb590b9cb2a6f388f3a4039a926ed0487f82f6215860443a4bef28fe10',
'3126b182f4115fe6705c36a43737cd38eaf3e600add0de49bd6823c7f0fa6a11',
'793fa91f40722794f2234fd5ab7904a94432f8376b5cef86e7440a457b3482c4',
'd1370e331d8752b53e6b07d560a3d0b9f03569e2686e786739e3a96cc2ae7eb8',
'e99e29d9953dea3642621c08e31bd78ab9a3648b630af7dcff2dd90bdbca08b8',
'5655477db1423bef022382ccfe88d2c597f64bc69df6f4f520adca95a29f94aa',
'3c719be7fbeed1101542c6420fde3f4e5fd773284effacf8f9f21e4a05e5d416'
],
blockVersion: '20000000',
nBits: '170cf4e3',
nTime: '6036f006'
}
// extraNonce2,nTime and nonce is received from miner (mining.submit)
let extraNonce2 = "301a0000"
let nTime = "6036f00a"
let nonce = "e4abf319"
let coinbaseBuffer = serializeCoinbase(blockTemplate.coinbase[0], blockTemplate.coinbase[1], blockTemplate.ExtraNonce1, extraNonce2);
let coinbaseHash = sha256d(coinbaseBuffer);
console.log(coinbaseHash.toString('hex'))
// result: a97941791004f1ad8fe01d9e1a0116b932e65c37b7de2bd29ebd238c0705aa72
let merkleRoot = MerkleRootWithCoinbase(blockTemplate.merkleTree,coinbaseHash.toString('hex'));
console.log(merkleRoot.toString('hex'))
// result: 7c1a57f3f75d94e3f1014afca791101e80eebed18b3bca014f798e7399f3ceef
let headerBuffer = blockHeader(blockTemplate.blockVersion, blockTemplate.previousblockhash, merkleRoot, nTime, blockTemplate.nBits, nonce);
console.log(headerBuffer.toString('hex'))
// result: 00000020ce60ed83fe3687073f8d5215ddcfa55e042bd70e44310a000000000000000000efcef399738e794f01ca3b8bd1beee801e1091a7fc4a01f1e3945df7f3571a7c0af03660e3f40c1719f3abe4
let headerHash = sha256d(headerBuffer);
console.log(headerHash.toString('hex'))
// result: 87d83009bcca8068760bbdf5130a19b88e0f120c17cb97590397aed4b62ef4e4
let headerHashReversed = reverseBuffer(headerHash);
console.log(headerHashReversed.toString('hex'))
// result: e4f42eb6d4ae97035997cb170c120f8eb8190a13f5bd0b766880cabc0930d887
result hash is not valid, don't understand where I have invalid calculation.
I fixed problem and you can find working example below:
const crypto = require('crypto');
sha256 = function(buffer){
var hash1 = crypto.createHash('sha256');
hash1.update(buffer);
return hash1.digest();
};
sha256d = function(buffer){
return sha256(sha256(buffer));
};
reverseBuffer = function(buff){
var reversed = new Buffer.alloc(buff.length);
for (var i = buff.length - 1; i >= 0; i--)
reversed[buff.length - i - 1] = buff[i];
return reversed;
};
reverseHex = function(hex){
return reverseBuffer(Buffer.from(hex, 'hex')).toString('hex');
};
serializeCoinbase = function(coinbase1, coinbase2, extraNonce1, extraNonce2){
var coinbase = coinbase1+
extraNonce1+
extraNonce2+
coinbase2;
return Buffer.from(coinbase, 'hex');
};
MerkleRootWithCoinbase = function(merkleTree,coinbaseHash){
var hash = coinbaseHash;
merkleTree.forEach(a => {
hash = sha256d(Buffer.from(hash + a,'hex')).toString('hex')
});
return hash
}
convertPreviousblockhash = function(previousblockhash){
return previousblockhash.match(/.{1,8}/g).reverse().join('')
}
blockHeader = function(blockVersion, previousblockhash, merkleRoot, nTime, nBits, nonce){
previousblockhash = convertPreviousblockhash(previousblockhash)
var hash = reverseHex(blockVersion)+
reverseHex(previousblockhash)+
merkleRoot+
reverseHex(nTime)+
reverseHex(nBits)+
reverseHex(nonce);
return Buffer.from(hash, 'hex');
};
// blockTemplate is received from pool (mining.notify)
let blockTemplate = {
ExtraNonce1: '929e4bb4',
ExtraNonce2_size: 4,
previousblockhash: '83ed60ce078736fe15528d3f5ea5cfdd0ed72b04000a31440000000000000000',
coinbase: [
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5c0332410a192f5669614254432f4d696e6564206279206c6576616d75732f2cfabe6d6def4d71fab9c4856256f87621806274036feb16b79c411157a920716275e722951000000000000000108ad5ef13de0027ea',
'ffffffff04ce4c6229000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3acac6189d090598816a3e1092b966aad3c6aef15e3c826a334cea7f21002fd6540000000000000000266a24b9e11b6d65ba1a6db71ac060e04c3cb5bf049af5478970ded266f93b2f5b89f86348c9fc0000000000000000266a24aa21a9edf2716abb1dd5ac06ad1fed7f6bde943fc1657a0fd53a1c11823a4ffb5cc823e400000000'
],
merkleTree: [
'628a4d82f4950e0d55407f231057e8524a27e724143667cfd6c4bb3bc323d75f',
'e1ec3b078e79bd6da16899697e47a1eff05f27304baf7bf4fb3784145b452343',
'dfac6e968c29d40b35f34474b081b3fb9a054497c8ce8cebfca36471e9b5f912',
'35ea339bda0105cd0401e5f4dd89c511a41ea34117d839d6d3e32b575f4e4dc0',
'97bd6bfb590b9cb2a6f388f3a4039a926ed0487f82f6215860443a4bef28fe10',
'3126b182f4115fe6705c36a43737cd38eaf3e600add0de49bd6823c7f0fa6a11',
'793fa91f40722794f2234fd5ab7904a94432f8376b5cef86e7440a457b3482c4',
'd1370e331d8752b53e6b07d560a3d0b9f03569e2686e786739e3a96cc2ae7eb8',
'e99e29d9953dea3642621c08e31bd78ab9a3648b630af7dcff2dd90bdbca08b8',
'5655477db1423bef022382ccfe88d2c597f64bc69df6f4f520adca95a29f94aa',
'3c719be7fbeed1101542c6420fde3f4e5fd773284effacf8f9f21e4a05e5d416'
],
blockVersion: '20000000',
nBits: '170cf4e3',
nTime: '6036f006'
}
// extraNonce2,nTime and nonce is received from miner (mining.submit)
let extraNonce2 = "301a0000"
let nTime = "6036f00a"
let nonce = "e4abf319"
let coinbaseBuffer = serializeCoinbase(blockTemplate.coinbase[0], blockTemplate.coinbase[1], blockTemplate.ExtraNonce1, extraNonce2);
let coinbaseHash = sha256d(coinbaseBuffer);
console.log(coinbaseHash.toString('hex'))
// result: a97941791004f1ad8fe01d9e1a0116b932e65c37b7de2bd29ebd238c0705aa72
let merkleRoot = MerkleRootWithCoinbase(blockTemplate.merkleTree,coinbaseHash.toString('hex'));
console.log(merkleRoot.toString('hex'))
// result: 7c1a57f3f75d94e3f1014afca791101e80eebed18b3bca014f798e7399f3ceef
let headerBuffer = blockHeader(blockTemplate.blockVersion, blockTemplate.previousblockhash, merkleRoot, nTime, blockTemplate.nBits, nonce);
console.log(headerBuffer.toString('hex'))
// result: 00000020ce60ed83fe3687073f8d5215ddcfa55e042bd70e44310a0000000000000000007c1a57f3f75d94e3f1014afca791101e80eebed18b3bca014f798e7399f3ceef0af03660e3f40c1719f3abe4
let headerHash = sha256d(headerBuffer);
console.log(headerHash.toString('hex'))
// result: 5edd9d1f993bb74a4f1b5fcd3ab48140df048af0c3bfa43c16c1000000000000
let headerHashReversed = reverseBuffer(headerHash);
console.log(headerHashReversed.toString('hex'))
// result: 000000000000c1163ca4bfc3f08a04df4081b43acd5f1b4f4ab73b991f9ddd5e
If the the miner is using version-rolling it means that miner is able to manipulate bits as specified in BIP320 - within mask 0x1fffe000.
Try including the version from the submit into block hash calculation.

About nodejs AES-128-GCM encryption after the other party can not parse the ciphertext problem

According to the project requirements, a set of request data needs to be encrypted using AES-128-GCM, but the encrypted data obtained is always not parsed by the other party.When I use Golang to write the encryption algorithm, the encrypted data can be parsed properly by the other party.
function Encrypt(text, key) {
if (!text) {
return '';
}
if (typeof text != 'string') {
text = JSON.stringify(text);
}
const md5 = crypto.createHash('md5');
const result = md5.update(key).digest();
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-128-gcm', result, iv);
cipher.setAutoPadding(0);
const encrypted = cipher.update(text, 'utf8');
const finalstr = cipher.final();
const tag = cipher.getAuthTag();
const res = Buffer.concat([encrypted, finalstr, tag]);
return res.toString('base64');
}
The above is the encryption code for nodejs, The key passed in '12f41deed45188c8061c840c643baede'
Now I can prove that there is no problem for the other party to parse the cipher text, then the problem can only be a problem in the encryption code of nodejs, please help me to look at the big guys, to solve the problem for me!
The following is the encryption code for golang 👇
// golang code
type User struct {
Ai string `json:"ai"`
Name string `json:"name"`
IdNum string `json:"idNum"`
}
func main() {
u := User{
Ai: "200000000000000001",
Name: "某二一",
IdNum: "110000190201010009",
}
str, err := json.Marshal(u)
if err == nil {
fmt.Println(string(str))
}
//NewGCM_encrypt("12f41deed45188c8061c840c643baede", "{\"ai\":\"200000000000000001\",\"name\":\"某二一\",\"idNum\":\"110000190201010009\"}")
}
func NewGCM_encrypt(keyv, s string) string {
key, _ := hex.DecodeString(keyv)
//key := hexStringToByte(keyv)
fmt.Println("key:", key)
plaintext := []byte(s)
//fmt.Println("key", string(key))
block, err := aes.NewCipher(key)
if err != nil {
panic(err.Error())
}
// Never use more than 2^32 random nonces with a given key because of the risk of a repeat.
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
//nonce, _ = hex.DecodeString("FA9F93725406FD0E4F621B01")
fmt.Printf("iv:%x\n", nonce)
//iv := []byte("123456789012")
aesgcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)
r := append(nonce, ciphertext...)
//fmt.Println("ciphertext:", ciphertext)
//fmt.Printf("ciphertext16str:%x\n", ciphertext)
//r, _ := hex.DecodeString(fmt.Sprintf("%x", ciphertext))
//fmt.Println("r:", r)
return base64.StdEncoding.EncodeToString(r)
}
We need to include the IV with the encrypted data, in addition to the auth tag, otherwise the other party will not be able to decrypt it.
I've updated to match the Golang code, so the Node.js Encrypt should be decrypted by the same code.
I've also added an example of decrypting the data in Node.js, this may be helpful in diagnosing any issues.
NB: Since the IV is random, we won't get the same exact output as the Golang code (or between calls), but it should decrypt correctly.
const crypto = require("crypto");
function Encrypt(text, key) {
if (!text) {
return '';
}
if (typeof text != 'string') {
text = JSON.stringify(text);
}
const cipherKey = Buffer.from(key, "hex");
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-128-gcm', cipherKey, iv);
const res = Buffer.concat([iv, cipher.update(text), cipher.final(), cipher.getAuthTag()]);
return res.toString('base64');
}
function Decrypt(input, key) {
input = input instanceof Buffer ? input: Buffer.from(input, "base64");
const tag = input.slice(input.length - 16, input.length);
const iv = input.slice(0, 12);
const toDecrypt = input.slice(12, input.length - tag.length);
const cipherKey = Buffer.from(key, "hex");
const decipher = crypto.createDecipheriv('aes-128-gcm', cipherKey, iv);
decipher.setAuthTag(tag);
const res = Buffer.concat([decipher.update(toDecrypt), decipher.final()]);
return res.toString('utf8');
}
const key = "12f41deed45188c8061c840c643baede"
const encrypted = Encrypt("Why then 'tis none to you; for there is nothing either good or bad, but thinking makes it so.", key);
console.log("Encrypted data:", encrypted);
console.log("Decrypted:", Decrypt(encrypted, key));

Unable to decrypt a pdf file Using 'aes-256-cbc' algorithm in node js

I am trying to decrypt a PDF file using node js,PDF file encrypted by third party using C# .
I am having a hard time because I keep getting this error:
D:\IMP\DevOps Implementation\New folder (2)> node index1.js
internal/crypto/cipher.js:172
const ret = this[kHandle].final();
^
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
at Decipheriv.final (internal/crypto/cipher.js:172:29)
at Object.AESCrypt.decrypt (D:\IMP\DevOps Implementation\New folder (2)\index1.js:12:18)
at D:\IMP\DevOps Implementation\New folder (2)\index1.js:57:24
at FSReqCallback.readFileAfterClose [as oncomplete] (internal/fs/read_file_context.js:63:3) {
library: 'digital envelope routines',
function: 'EVP_DecryptFinal_ex',
reason: 'bad decrypt',
code: 'ERR_OSSL_EVP_BAD_DECRYPT'
}
we are using below code for encryption(C#)
private void FileEncrypt(string inputFile, string outputfile, string password)
{
byte[] salt = GenerateSalt();
byte[] passwords = Encoding.UTF8.GetBytes(password);
RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwords, salt, 50000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
AES.Padding = PaddingMode.Zeros;
using (FileStream fsCrypt = new FileStream(inputFile + ".aes", FileMode.Create))
{
fsCrypt.Write(salt, 0, salt.Length);
using (CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
using (FileStream fsIn = new FileStream(inputFile, FileMode.Open))
{
byte[] buffer = new byte[1048576];
int read;
while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
{
cs.Write(buffer, 0, read);
}
}
}
}
}
we are using below code for decryption(Node js)
AESCrypt.encrypt = function(cryptkey, iv, cleardata) {
var encipher = crypto.createCipheriv('aes-256-cbc', cryptkey, iv);
return Buffer.concat([
encipher.update(cleardata),
encipher.final()
]);
}
function decrypted(){
var enc;
fs.readFile('./resource/test.pdf', function (err,data) {
if (err) {
return console.log(err);
}
var bufferenc = new Buffer.from(data);
var dec = AESCrypt.decrypt(cryptkey,iv, bufferenc);
console.log(dec);
// var buffer = new Buffer.from(dec);
fs.writeFileSync('./resource/decrypted.pdf',dec);
});
}
Unable to decrypt a pdf file Using 'aes-256-cbc' algorithm in node js
You can try this code to decrypt the pdf data, it's working for me with the C# code:
const fs = require('fs');
const crypto = require("crypto");
function FileDecrypt(inputFile, outputfile, password)
{
// Read the entire file into the buffer.
let buffer = fs.readFileSync(inputFile);
// Read the first eight bytes as a salt.
let salt = buffer.slice(0,8);
let cipherText = buffer.slice(8);
// use key derivation function to get key and iv.
let derivedBytes = crypto.pbkdf2Sync(password, salt, 50000, 48, "sha1");
let key = derivedBytes.slice(0, 32);
let iv = derivedBytes.slice(32);
let cipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
// Switch off auto padding in this context
cipher.setAutoPadding(false);
let decryptedData = Buffer.concat([cipher.update(cipherText), cipher.final()]);
fs.writeFileSync(outputfile, decryptedData);
}
FileDecrypt("encrypted.pdf", "node-decrypted.pdf", "password");

Generate digital signature with nodejs for Alipay API

I am implenting ALIPAY API, to my project. In order to make some request I need to generate a signature with RSA private.pem - public.pem. The documentions is only showing JAVA exemple, but I need to implement it in NodeJS. So I try using crypto.sign but cannot get this to work.
Here is the JAVA code :
const g = (content, privateKey, charset) => {
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(priPKCS8);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(content.getBytes(charset));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (Exception e) {
throw new RuntimeException("the key's format maybe not right");
}
}
Can any one help to convert this into NodeJS code.
HERE IS MY WORKING SOLUTIONS:
/**
* #param {*} // let parameters = new Map(); parameters.set("service", "alipay.overseas.secmerchant.maintain.queryStatus");
*/
signRSA: async (parameters) => {
let unnecessaryParams = Array.from(parameters.entries())
.filter(a1 => a1[1] === undefined || a1[1].length === 0 || a1[1] === "null")
.map(a1 => a1[0]);
unnecessaryParams.forEach(a1 => parameters.delete(a1));
let charset = ["_input_charset", "charset"].filter(a1 => parameters.has(a1)).map(a1 => parameters.get(a1))[0];
charset = charset === undefined ? "UTF-8" : charset;
//MD5,RSA,RSA2
let signContent = Array.from(parameters.entries())
.filter(a1 => {
return a1[0] !== "sign" && a1[0] !== "sign_type";
})
.sort((a1, a2) => a1[0].localeCompare(a2[0]))
.map(a1 => a1[0] + "=" + a1[1]).join("&");
let key = fs.readFileSync('./services/alipay/rsa_private_key.pem');
// let RSA_sign = r.sign_it(signContent, key, "RSA2")
let RSA_sign = crypto.createSign('RSA-SHA256')
RSA_sign.update(signContent)
let s1 = RSA_sign.sign(key, "base64")
parameters.set("sign", s1);
return Array.from(parameters.entries()).map(a1 => a1[0] + "=" + encodeURIComponent(a1[1])).join("&");
}
I've created a Node.js version of this code, it should sign content using the provided private key.
I've also created a verifySignature function to check this, and created the equivalent code in Java (using your code as a template), just to ensure we're getting the same result in both languages.
Node.js
const crypto = require("crypto");
// Sample keys I've generated
const privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAICFIt6pLMSNn9scI/h85V/DamtsFqfN4DB+VSPpGMAcwEByKVsiBzdX7JJsr8uquoVWEmFPwymxUVwIy+MOK03QEaQDns0W/SeVNXyWvDTO2w65vV9hIEO1VyiQiBA8n0yoQGfbn2KBnv7SggJDpy10cF3lx5SdHV96lF+qyBs7AgMBAAECgYAQEJRLQNpXt1xEB0B3zyTc05B53Qj38MQVS7VYx8pQOUfOxnZk7dv5DwSDSRKwYqbuA9RIVbAPhhqlZnQV7a9722QBFcEKLh6/65HCX248t/v6/x1kj9p6rcbLuFuakjKXs4AznWmb4YV2Flh9/qx9keUzdS5/UPJZVU2+grAAAQJBAO/3JjTRgiGQxEdA4MVpfs0lh/+gzbDszL1In5USAPKyGPDqYIktAfF3cBc7BKyK3kBenJi3S5Qv5R3u2Ly6H0cCQQCJG5oAvEt8JPL7ZyxHzdTP7+7hWXRGOyK2Ar0CO3fbg+SGls5liJY3HNHc0lrdyG1A+5Z6AKUNYCgfbOhqmeZtAkEA5CzhUoYJNDuAt7Q0RuLqZM2URPk1vU9d23qr68ajyiKZXrOuuaFnYKDOn/hJmHuvnAua4gggwLbOKSlNRB/CzwJAKedztBHYiELKuKeZ0wBHsJ3GRr2OWgCs5TAFEG+YfFDdQX1J66JJNuLqCTGJcAtXyOqb3QHhcCsZWDFy/1G2KQJBAKRnm7D0BHLOK37Xp3MxGva4tFP6VPdqjgIVBW3PM8BcwTcw8VML3kkFh2y0gJdLRxzFwDDJgfG6Cxp8i1gqB+4=";
const publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAhSLeqSzEjZ/bHCP4fOVfw2prbBanzeAwflUj6RjAHMBAcilbIgc3V+ySbK/LqrqFVhJhT8MpsVFcCMvjDitN0BGkA57NFv0nlTV8lrw0ztsOub1fYSBDtVcokIgQPJ9MqEBn259igZ7+0oICQ6ctdHBd5ceUnR1fepRfqsgbOwIDAQAB";
const content = "We are such stuff as dreams are made on, and our little life is rounded with a sleep";
const signature = signContent(content, base64KeyToPEM(privateKey, "PRIVATE"), "utf8");
console.log("Signature:", signature);
console.log("Verify signature:", verifySignature(content, base64KeyToPEM(publicKey, "PUBLIC"), signature, "utf8"));
function signContent(content, privateKey, encoding) {
const sign = crypto.createSign("SHA256");
sign.write(content, encoding);
sign.end();
return sign.sign(privateKey, "base64");
}
function verifySignature(content, publicKey, signature, encoding) {
const verify = crypto.createVerify("SHA256");
verify.write(content, encoding);
verify.end();
return verify.verify(publicKey, Buffer.from(signature, "base64"));
}
function base64KeyToPEM(base64Key, keyType) {
return [`-----BEGIN ${keyType} KEY-----`, ...splitStringIntoChunks(base64Key, 64), `-----END ${keyType} KEY-----`].join("\n");
}
function splitStringIntoChunks(input, chunkSize) {
const chunkCount = Math.ceil(input.length / chunkSize)
return Array.from( { length: chunkCount } ).map((v, chunkIndex) => input.substr(chunkIndex * chunkSize, chunkSize));
}
Java
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
public class DigitalSignature {
public static void main(String[] args)
{
try {
// Sample keys I've generated
final String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAICFIt6pLMSNn9scI/h85V/DamtsFqfN4DB+VSPpGMAcwEByKVsiBzdX7JJsr8uquoVWEmFPwymxUVwIy+MOK03QEaQDns0W/SeVNXyWvDTO2w65vV9hIEO1VyiQiBA8n0yoQGfbn2KBnv7SggJDpy10cF3lx5SdHV96lF+qyBs7AgMBAAECgYAQEJRLQNpXt1xEB0B3zyTc05B53Qj38MQVS7VYx8pQOUfOxnZk7dv5DwSDSRKwYqbuA9RIVbAPhhqlZnQV7a9722QBFcEKLh6/65HCX248t/v6/x1kj9p6rcbLuFuakjKXs4AznWmb4YV2Flh9/qx9keUzdS5/UPJZVU2+grAAAQJBAO/3JjTRgiGQxEdA4MVpfs0lh/+gzbDszL1In5USAPKyGPDqYIktAfF3cBc7BKyK3kBenJi3S5Qv5R3u2Ly6H0cCQQCJG5oAvEt8JPL7ZyxHzdTP7+7hWXRGOyK2Ar0CO3fbg+SGls5liJY3HNHc0lrdyG1A+5Z6AKUNYCgfbOhqmeZtAkEA5CzhUoYJNDuAt7Q0RuLqZM2URPk1vU9d23qr68ajyiKZXrOuuaFnYKDOn/hJmHuvnAua4gggwLbOKSlNRB/CzwJAKedztBHYiELKuKeZ0wBHsJ3GRr2OWgCs5TAFEG+YfFDdQX1J66JJNuLqCTGJcAtXyOqb3QHhcCsZWDFy/1G2KQJBAKRnm7D0BHLOK37Xp3MxGva4tFP6VPdqjgIVBW3PM8BcwTcw8VML3kkFh2y0gJdLRxzFwDDJgfG6Cxp8i1gqB+4=";
final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAhSLeqSzEjZ/bHCP4fOVfw2prbBanzeAwflUj6RjAHMBAcilbIgc3V+ySbK/LqrqFVhJhT8MpsVFcCMvjDitN0BGkA57NFv0nlTV8lrw0ztsOub1fYSBDtVcokIgQPJ9MqEBn259igZ7+0oICQ6ctdHBd5ceUnR1fepRfqsgbOwIDAQAB";
final String content = "We are such stuff as dreams are made on, and our little life is rounded with a sleep";
String signature = signContent(content, privateKey, "UTF-8");
System.out.println("Signature: " + signature);
System.out.println("verifySignature: " + verifySignature(signature, content, getPublicKeyFromBase64(publicKey), "UTF-8"));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
private static PublicKey getPublicKeyFromBase64(String publicKeyBase64) throws Exception {
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBase64)));
}
public static String signContent(String content, String privateKey, String charset) throws Exception
{
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(priPKCS8);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(content.getBytes(charset));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (Exception e) {
throw new RuntimeException("signContent: Exception occurred: " + e.getMessage());
}
}
public static Boolean verifySignature(String signatureBase64, String content, PublicKey publicKey, String charset) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes(charset));
return signature.verify(Base64.getDecoder().decode(signatureBase64));
}
}

Secure token created in node different from .net

I'm trying to implement reCaptcha Secure Tokens in nodejs.
Looked at the examples made in Java and in .NET and created this version for node:
exports.getSecureToken = function() {
var algorithm = 'aes-128-ecb';
var tokenObj = { session_id: 'ab0069ec-3c2c-436c-868b-43c7a10db229'/*uuid.v4()*/, ts_ms: 1446560931992/*(new Date()).getTime()*/ };
var text = JSON.stringify(tokenObj);
var shaHash = new Buffer(crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO'/*config.reCAPTCHASecret*/).digest('hex'), 'hex');
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipher(algorithm, key, key);
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return result;
};
Problem is that in .NET I get a token that works (using the right key not included in the post), but in node I get a different token for the same input, and it doesn't works:
.NET - LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
node - EGr7drd1JEylwzLGakZ6dpPRSf2nFdpzHOrJlLZlyHYmVRj5obAw7WjPt4W5l0vsywNEqCQ-2_d7qIZOMiOedianfBrQPOBaOmmq44IOB8Q
I got to see that key and input are the same (in .NET and node) right at the moment before encryption, so the problem must(?) be the cipher, any clues?
.NET code for reference:
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine(EncryptJsonToken(GetJsonToken()));
}
public static string GetJsonToken()
{
//Example: {"session_id": e6e9c56e-a7da-43b8-89fa-8e668cc0b86f,"ts_ms":1421774317718}
string jsonRequest = "{" + string.Format("\"session_id\": {0},\"ts_ms\":{1}", "ab0069ec-3c2c-436c-868b-43c7a10db229", 1446560931992) + "}";
return jsonRequest;
}
public static byte[] getKey()
{
string secretKey = "6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO";
SHA1 sha = SHA1.Create();
byte[] dataToHash = Encoding.UTF8.GetBytes(secretKey);
byte[] shaHash = sha.ComputeHash(dataToHash);
byte[] first16OfHash = new byte[16];
Array.Copy(shaHash, first16OfHash, 16);
return first16OfHash;
}
public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
public static string EncryptJsonToken(string jsonToken)
{
byte[] encrypted = EncryptStringToBytes_Aes(jsonToken, getKey(), getKey());
//Base64 encode the encrypted data
//Also applys the URL variant of base64 encoding, unfortunately the HttpServerUtility.UrlTokenEncode(encrypted) seems to truncate the last value from the string so we can't use it?
return Convert.ToBase64String(encrypted, Base64FormattingOptions.None).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
}
To debug in .NET: DEMO
You have two problems:
You're using JSON.stringify() to produce a valid JSON string, but the GetJsonToken() method in the C# code doesn't produce a valid JSON string. There are " missing for the UUID and there is a space between the session_id key and its value for some reason. You have to reflect those differences in JavaScript:
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
There is no such function crypto.createCipher(algorithm, key, key). There is however crypto.createCipheriv(algorithm, key, iv). createCipher(algorithm, password) can be used if one has a password instead of a key which you don't have. Since there is no IV for ECB mode, you can pass in an empty (binary) string as the IV.
Full code:
var crypto = require("crypto");
var algorithm = 'aes-128-ecb';
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
console.log("Token: " + text);
var shaHash = crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO').digest();
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipheriv(algorithm, key, "");
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log("result: " + result);
console.log("expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI");
Output:
Token: {"session_id": ab0069ec-3c2c-436c-868b-43c7a10db229,"ts_ms":1446560931992}
result: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI

Resources