I'm trying (and failing) to decipher in Delphi usung LockBox 3 a message that was encrypted using Node.js' crypto library.
node.js code:
var crypto = require('crypto');
var cipher = crypto.createCipher('aes-256-ctr', 'my password');
var crypted = cipher.update('hello world', 'utf8', 'base64');
crypted += cipher.final(output_encoding);
console.log(crypted);
The result from that is
oyC1KRVx3JZBLlI=
Delphi code:
var
Codec: TCodec;
CipherText: AnsiString;
begin
Codec := TCodec.Create(nil);
try
Codec.CryptoLibrary := TCryptographicLibrary.Create(Codec);
//
Codec.StreamCipherId = 'native.StreamToBlock';
Codec.BlockCipherId = 'native.AES-256';
Codec.ChainModeId = 'native.CTR';
//
Codec.Password := 'my password';
Codec.DecryptAnsiString(CipherText, 'oyC1KRVx3JZBLlI=');
//
Result := string(CipherText);
finally
Codec.Free;
end;
end;
What am I missing?
What is the problem?
The problem is that both libraries use different keys and initialization vectors (IVs) internally.
Remember that AES cipher does not work with passwords, but keys and IVs.
When you provide a password to the cryptographic library it uses some internal mechanism to automatically derive a key and IV.
This mechanism is not obvious, but is usually described in documentation of cryptographic libraries.
Documentation of crypto module in node.js says it is using the EVP_BytesToKey function of OpenSSL to derive the key and IV:
crypto.createCipher(algorithm, password) - Creates and returns a Cipher
object that uses the given algorithm and password.
...
The password is used to derive the cipher key and initialization
vector (IV). The value must be either a 'binary' encoded string or a
[Buffer[].
The implementation of crypto.createCipher() derives keys using the
OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5,
one iteration, and no salt. The lack of salt allows dictionary attacks
as the same password always creates the same key. The low iteration
count and non-cryptographically secure hash algorithm allow passwords
to be tested very rapidly.
Quote from Node.js v5.6.0 Documentation.
How to solve the problem?
The proper solution is to use a cryptographically secure hash algorithm to derive key from the password, and then manually provide the keys and IVs to the cryptographic library, whichever it is.
A quick and dirty (and highly insecure) solution is to find a Delphi routine that is equivalent to EVP_BytesToKey and just use that to make it work.
Remember to also check that you are using the same padding scheme. TCodec should allow you to choose PaddingScheme of padPKCS, which should be compatible with the one used by the crypto module in node.js. If it does not work try other options too.
Another option is to use OpenSSL in Delphi, which should already be compatible with what is used in node.js.
Also, see this question with similar problem to yours:
TPLB 3 OpenSSL Decrypt AES-256-CBC Encrypted with Ruby 2.0.0 OpenSSL::Cipher
Related
I am working with Docusign connect and planning to use HMAC keys to authenticate the messages. I am referring https://developers.docusign.com/esign-rest-api/guides/connect-hmac#example-hmac-workflow link.
I find few terms confusing in the documentation. Attaching the code snippet from the doc for python.
def ComputeHash(secret, payload):
import hmac
import hashlib
import base64
hashBytes = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest()
base64Hash = base64.b64encode(hashBytes)
return base64Hash;
def HashIsValid(secret, payload, verify):
return verify == ComputeHash(secret,payload)
Can you explain what payload(didn't understand exactly what it is), secret (I am guessing the secret key) and verify means from the above code and how do I verify my secret key with X-Docusign-Signature-1 which I get from response header?
My code:
message = request.headers
hashBytes = hmac.new(secret_key.encode('utf-8'), msg=message.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
base64Hash = base64.b64encode(hashBytes)
[Edited]
I found the solution on my own. Please read the first answer. I have explained it in details.
Sorry for the confusion.
Payload is "The entire body of the POST request is used, including line endings."
This is what you're encoding here using a Hash (HMAC) function.
SHA256 HMAC digest take in an the array of bytes (payload) and a secret (some key to use for encryption) and produces some encrypted version of the payload that can later be verified.
I highly recommend you ensure you first understand how the Connect webhook works without using HAMC encoding. This feature is meant to secure your application and it's a bit more complex. If you first get it working without it - you'll get a better grasp of what's going on (as well as feel a bit better about accomplishing a subtask).
Once you have it working, you can add the HMAC to make it secure and it will be easier at that point.
I found the solution to my problem.
expected_signature = request.headers['X-Docusign-Signature-1']
message = request.data # It is already in bytes. No need to encode it again.
hashBytes = hmac.new(secret_key.encode('utf-8'), msg=message, digestmod=hashlib.sha256).hexdigest()
actual_signature = base64.b64encode(hashBytes)
hmac.compare_digest(actual_signature.decode('utf-8'),expected_signature):
I'm trying to send email and password to API. The password needs to be encrypted with sha256.
So far I tried 2 packages - flutter_string_encryption and crypto but can't get it to work. With the first package, I can't find the sha256 method and with the second package, I'm getting an error when decoding List<Int> into a String. What is the proper way to do this?
The crypto documentation is straight forward. You can perform sha256 hashing as follows. If it doesn't solve what you are looking for, please add a minimum code that can reproduce the problem you are facing.
// import the packages
import 'package:crypto/crypto.dart';
import 'dart:convert'; // for the utf8.encode method
// then hash the string
var bytes = utf8.encode("foobar"); // data being hashed
var digest = sha256.convert(bytes);
print("Digest as hex string: $digest");
Following code give bad decryption error
vaultEngine.AESDecrypt = function (encKey, data) {
var cipherObject = crypto.createDecipheriv('aes-256-cbc', encKey, "a2xhcgAAAAAAAAAA");
var Fcontent = cipherObject.update(data, vaultEngine.outputEncoding, vaultEngine.inputEncoding);
Fcontent += cipherObject.final(vaultEngine.inputEncoding);
//console.log("Decryption data is:"+Fcontent);
return Fcontent;
}
Specifically this error:
TypeError: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decr
ypt
FIRST OF ALL
I'm concerned that your IV is hard coded directly into your method, which suggests that you're using the same IV for every encryption, which is bad bad bad. The IV should be cryptographically random (unpredictable), and different for every encryption. You can store it with your encrypted text and then pull it back out to use to decrypt, but you should not be using the same IV. If you're making this level of error, it suggests you need to do a lot more research on how to use encryption appropriately so that it actually protects the data you intend to protect. Start here.
And now to attempt to fix address your question directly:
According to the docs it looks like you've reversed your input encoding and output encoding variables, it should be:
var Fcontent = cipherObject.update(data, vaultEngine.inputEncoding, vaultEngine.outputEncoding);
Fcontent += cipherObject.final(vaultEngine.outputEncoding);
... if that doesn't work, I'd recommend the following changes:
use the stream processing write() and end() methods on your cipherObject, instead of the legacy update() and final() methods. The crypto module is considered "unstable" specifically because of the update to use node streams (see here), the legacy methods may remain but they'd be the first on the chopping block if breaking changes are introduced.
Create a buffer from your data before sending it to be decrypted. This will ensure that you've created your buffer correctly, and will minimize the work required at the decryption stage:
var dataBuffer = new Buffer(data, vaultEngine.inputEncoding);
cipherObject.write(dataBuffer);
cipherObject.end();
return cipherObject.read().toString(vaultEngine.outputEncoding);
NODE.JS CODE (DOES NOT WORK AS EXPECTED)
var crypto = require('crypto');
var input = '200904281000001|DOM|IND|INR|10|orderno_unique1|others|http://localhost/sample/Success.php|http://localhost/sample/failure.php|TOML';
var Key = "qcAHa6tt8s0l5NN7UWPVAQ==";
Key = new Buffer(Key || '', 'base64');
var cipher = crypto.createCipher('aes128', Key);
var actual = cipher.update(input, "utf8", "base64");
actual += cipher.final("base64");
console.log(actual);
Actual Output
bIK4D0hv2jcKP3eikoaM7ddqRee+RrT2FDOZA+c2sldyrqP+NrmgYOEXklUrSBQiU7w7e90nzFl/mpidy/Q8FD692bFLnESiNqGEQ7er44BXxFtNo6AKvpuohz31zm9JupJXL3jhOC+47mvDHokR4b9euDzPFitTJQW55JuSyvJphOKdiXjH+lGKxXKWsODq
Expected Output
ncJ+HX6zIdrUfEedi7YC82QOUARkySblivzysFbMqaYEMPj7UfMlE4SEkDcjg+D9dE5StGJgebSOkL7UuR6fXwodcgL0CSRds0Y+hX27gKUZK45b7Tc0EjXhepwHJ/olSdWUCkwRcZcv+wxtYzOH7+KKijJabJkU1/SF1ugExzcnqfV2wOZ9q79a4y/g3cb5
PHP CODE (WORKS AS EXPECTED)
include('CryptAES.php');
//Testing key
$Key = "qcAHa6tt8s0l5NN7UWPVAQ==";
//requestparam Testing - TOML
$input ="200904281000001|DOM|IND|INR|10|unique_10005|others|http://www.yourdomain.com/paymentSuccess.php|http://www.yourdomain.com/paymentFailure.php|TOML";
$aes = new CryptAES();
$aes->set_key(base64_decode($key));
$aes->require_pkcs5();
echo $aes->encrypt($input);
There is a known issue with using PHP's inbuilt mcrypt library. It pads the key in a different manner as node.js. This issue was bugging me a lot a few months ago, and there is a workaround here. What I did was use a small php command line script with my node.js app to handle encryption and decryption.
First of all, your two input strings are different. On Node.js, you are using
200904281000001|DOM|IND|INR|10|orderno_unique1|others|
http://localhost/sample/Success.php|
http://localhost/sample/failure.php|TOML
whereas on PHP it's:
200904281000001|DOM|IND|INR|10|unique_10005|others|
http://www.yourdomain.com/paymentSuccess.php|
http://www.yourdomain.com/paymentFailure.php|TOML
They differ in the domain as well as the second-last string in the first line. Then, you do not explain where your CryptAES function in PHP comes from. As you do not specify any parameters, one can only guess that it's AES with 128 bits. What are its defaults? What kind of padding is used? CBC mode or EBC mode? …?
Questions over questions one can not answer for now.
For testing, I wrote a small Node.js script that takes both of your input strings, and tries all ciphers available in Node.js (you can get them using crypto.getCiphers()) combined with all possible encodings Node.js supports (i.e., utf8, ascii, utf16le and ucs2). None of them resulted in the string you gave as expected.
So, although this is not a real answer, I hope that this helps you anyway, at least a little step into the right direction.
I am using NodeJS's bundled crypto module for SHA256 hashing on the server-side.
On the client-side, I am using a javascript library called Crypto-JS.
I am using SHA256 hashes for a login system that uses classical nonce-based authentication. However, my server-side and client-side hash-digests don't match up even when the hash-messages are the same (I have checked this). Even the length of the hash-digests are different.
This is a snippet of my client-side implementation:
var password_hash = CryptoJS.SHA256( token.nonce /*this is the server's nonce*/ + cnonce + password ).toString(CryptoJS.enc.Base64);
This is a snippet of my server-side implementation:
var sha256 = CRYPTO.createHash("sha256");
sha256.update(snonce+cnonce+password, "utf-8");
var hash = sha256.digest("base64");
This is some sample data:
client-digest: d30ab96e65d09543d7b97d7cad6b6cf65f852f5dd62c256595a7540c3597eec4
server-digest: vZaCi0mCDufqFUwVO40CtKIW7GS4h+XUhTUWxVhu0HQ=
client-message: O1xxQAi2Y7RVHCgXoX8+AmWlftjSfsrA/yFxMaGCi38ZPWbUZBhkVDc5eadCHszzbcOdgdEZ6be+AZBsWst+Zw==b3f23812448e7e8876e35a291d633861713321fe15b18c71f0d54abb899005c9princeofnigeria
server-message: O1xxQAi2Y7RVHCgXoX8+AmWlftjSfsrA/yFxMaGCi38ZPWbUZBhkVDc5eadCHszzbcOdgdEZ6be+AZBsWst+Zw==b3f23812448e7e8876e35a291d633861713321fe15b18c71f0d54abb899005c9princeofnigeria
Does anyone know why the hashes are different? I thought that if it is the same protocol/algorithm, it will always produce the same hash.
Edit: Wow. I went to this online hashing tool and it produces yet another digest for the same message:
4509a6d5028b217585adf41e7d49f0e7c1629c59c29ce98ef7fbb96c6f27502c
Edit Edit: On second thought, the reason for the online hashing tool being different is probably because it uses a hex encoding and I used base64
The problem was indeed with encodings.
Look at the client-side implementation:
var password_hash = CryptoJS.SHA256(message).toString(CryptoJS.enc.Base64);
The CryptoJS.enc.Base64 parameter actually requires another component in the CryptoJS library that I did not include (stored in a js file: enc-base64-min.js). So, in the absence of a valid encoding type, it defaulted to hex.
Thanks #dhj for pointing out the encoding issue!
The problem is that your client produces hex-encoded digest, while server uses base64 encoding.