I have to encrypt string in Delphi with Twofish/CBC algorithm, send it to server and decrypt there. I've tested the code below, and B64 encoding/decoding process works, however I'm stuck at cipher encryption/decryption.
I am using DEC 5.2 for Delphi.
Here is the Delphi code that does the encryption:
class function TEncryption.EncryptStream(const AInStream: TStream; const AOutStream: TStream; const APassword: String): Boolean;
var
ASalt: Binary;
AData: Binary;
APass: Binary;
begin
with ValidCipher(TCipher_Twofish).Create, Context do
try
ASalt := RandomBinary(16);
APass := ValidHash(THash_SHA1).KDFx(Binary(APassword), ASalt, KeySize);
Mode := cmCBCx;
Init(APass);
EncodeStream(AInStream, AOutStream, AInStream.Size);
result := TRUE;
finally
Free;
ProtectBinary(ASalt);
ProtectBinary(AData);
ProtectBinary(APass);
end;
end;
class function TEncryption.EncryptString(const AString, APassword: String): String;
var
instream, outstream: TStringStream;
begin
result := '';
instream := TStringStream.Create(AString);
try
outstream := TStringStream.Create;
try
if EncryptStream(instream, outstream, APassword) then
result := outstream.DataString;
finally
outstream.Free;
end;
finally
instream.Free;
end;
end;
And PHP function that is supposed to decrypt sent data:
function decrypt($input, $key) {
$td = mcrypt_module_open('twofish', '', 'cbc', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$decrypted_data = mdecrypt_generic($td, base64_decode_urlsafe($input));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $decrypted_data;
}
I beleive I have to fiddle a bit more with salt and initialization vectors, however I have no idea how. From what I understand, KDFx() function makes SHA1 hashed password out from user password and salt, but I'm pretty much stuck at that point.
KDFx is an proprietary incompatible key derivation method, from the source: "class function TDECHash.KDFx // DEC's own KDF, even stronger". You will have problens using this from DEC and PHP and should use other libraries. The problem is discussed e.g. in http://www.delphipraxis.net/171047-dec-5-2-mit-vector-deccipher-umbau-von-dot-net-auf-delphi.html
Related
I have this code in PHP (can't modify)
<?php
$myPlain = "123456789012345678900000";
$myKey = md5($myPlain, true);
$myKey .= substr($myKey, 0,8);
$encrypted = openssl_encrypt($myPlain, 'des-ede3', $myKey, OPENSSL_RAW_DATA);
print(base64_encode($encrypted));
This code returns
FTYDrZTZMjVBv5Fk/xcfFxJASaizzrmoPts7fGDvWjc=
When I try to replicate this in NodeJS
function testEde3(myKey, myPlain) {
try {
let md5Key = crypto.createHash('md5').update(myKey, 'utf-8').digest("base64").substr(0, 24);
console.log(md5Key); //outputs 4o1aJrSWN3bSfjuIX6VXgA==
console.log(md5Key.length); //outputs 24
const cipher = crypto.createCipheriv('des-ede3', md5Key, null);
let encrypted = cipher.update(myPlain, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
} catch ( ex ) {
return ex;
}
}
const myKey = "123456789012345678900000";
const myPlain = "123456789012345678900000";
const hash = testEd3(myKey, myPlain);
console.log(`Hash is: ${hash}`);
The output is
Hash is: lDQX9OGsopKOt6P9WQwekGsKDQGFpfGW50zbs3HrOfQ=
I'm thinking the problem is on MD5. If I try to encrypt without md5, the results are the same.
Thanks
The key in Php code consists of the 16 bytes of the MD5 hash, to which the first 8 bytes of the MD5 hash are appended, resulting in a 24 bytes key (as required for 3DES). This is currently not implemented in the NodeJS code, but can be achieved e.g. by:
let md5Key = crypto.createHash('md5').update(myKey, 'utf-8').digest();
md5Key = Buffer.concat([md5Key, md5Key.slice(0, 8)]);
With this change, the NodeJS code generates the ciphertext of the PHP code with the same input data.
des-ede3 means 3DES in ECB mode. Note that 3DES is outdated and slow and should be replaced by AES. ECB mode is generally insecure. Encrypting the key also makes little sense (but may only be for testing purposes).
Setup:
session_start();
function set_encryption_method() {
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > 3600) {
unset($_SESSION['cipher']);
unset($_SESSION['iv']);
unset($_SESSION['last_activity']);
}
$cipher = 'aes-256-cbc';
$iv = random_bytes(16);
if (in_array($cipher, openssl_get_cipher_methods())) {
if (!isset($_SESSION['cipher'])) {
$_SESSION['cipher'] = $cipher;
}
if (!isset($_SESSION['iv'])) {
$_SESSION['iv'] = $iv;
}
$_SESSION['last_activity'] = time();
} else {
die('Encryption method not supported!');
}
}
set_encryption_method();
Encrypt:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
return rawurlencode(base64_encode(openssl_encrypt($string, $_SESSION['cipher'], $key, 0, $_SESSION['iv'])));
}
Decrypt:
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
return openssl_decrypt(rawurldecode(base64_decode($encrypted)), $_SESSION['cipher'], $key, 0, $_SESSION['iv']);
}
When decrypt_string() is called with the appropriate parameters, it throws this error: digital envelope routines evp_decrypt_final_ex: bad decrypt. If I hardcode the iv, then it works correctly.
What am I doing wrong?
The error message is (indirectly) caused by the fact that your are using different IVs for encryption and decryption. From your description it is not clear how that can happen, but let me propose some suggestions that will avoid your issue altogether.
First, with EAS-CBC it is not a good idea to use the same IV + key combination multiple times. You can find some discussion on that in the answers to Is AES in CBC mode secure if a known and/or fixed IV is used?. You did not mention how you are using the different functions, but during 3600 seconds, you are using the same IV + key combinations.
To work around this, you could generate a random IV for every encryption that you do. You could then store the IV together with the encrypted data; the IV is not required or supposed to be secret. The following two functions are modifications of yours that do exactly that, by concatenating the two after encryption and splitting the two before decryption:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
$iv = random_bytes(16);
$ciphtxt = openssl_encrypt($string, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv.$ciphtxt);
}
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
$combo = base64_decode($encrypted);
$iv = substr($combo, 0, 16);
$ciphtxt= substr($combo, 16);
return openssl_decrypt($ciphtxt, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
}
Note the use of the flag OPENSSL_RAW_DATA. As the documentation for openssl_encrypt mentions (not too clearly), the result will be base64-ed if you do not give that flag. You were doing the base64-ing yourself so I added the flag. This also makes it a little easier to handle the (de-)concatenation of the IV and the ciphertext.
A few words about the occurrence of that bad decrypt error: that is emitted when the padding bytes are different than expected. Since you were using an incorrect IV for decryption, the AES decryption did not result in the original data, with a high probability that the padding bytes therefore did not have the right value.
First point to address: Why are you using URL encode/decode?
Those are for passing parameters through browsers; and the browser generally does the decoding for you so as a base rule - if you've written "urldecode" in your PHP, you've probably gone wrong as you're possibly decoding something already decoded. (Or are not using in the right place, or using when unnecessary)
However, Base64 is (or should be) url-safe, so I'd suggest knocking out the urlencode/decode and see what happens.
Second point to address: you need to undo the urlencode/base64 conversion in the reverse order you set.
So rawurlencode(base64_encode(openssl_encrypt( reversed is openssl_decrypt(base64_encode(rawurldecode (you have the base64 and url decode flipped). But knocking out the urlencode/decode entirely (unless you're adamant you need it) will rectify this too.
My problem is as follows:
I have a PHP script that is responsible for encrypting a string using AES-256-CBC encryption. This script uses the openssl lib and returns an X result.
<?php
class AES
{
const PRIVATE_KEY = 'abcdefghijklmnnoabcdefghijklmnno';
const ENCRYPT_METHOD = 'aes-256-cbc';
const VECTOR = 'abcdefghijklmnno';
public function encryptData($data)
{
while(strlen($data) < 16) $data .= "\0";
return openssl_encrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, OPENSSL_ZERO_PADDING, self::VECTOR);
}
public function encryptDataL($data)
{
return openssl_encrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, 0, self::VECTOR);
}
public function decryptData($data)
{
return openssl_decrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, OPENSSL_ZERO_PADDING, self::VECTOR);
}
}
$aes = new AES();
echo $aes->encryptData("abcdefghijkl");
echo "\n";
echo $aes->encryptDataL("{\"REQUEST\": [{\"MSISDN\": \"32156489721\",\"IDPRODUCT\": 123,\"IDOPERATOR\": 12345,\"OUTPUTFORMAT\": \"JSON\"}],\"OUTPUTFORMAT\": \"json\"}");
?>
when I run a JS script, responsible for doing the same, but using the Crypto lib, the result obtained is different from the previous X.
const crypto = require('crypto');
const cipher = crypto.createCipheriv('aes-256-cbc', 'abcdefghijklmnnoabcdefghijklmnno', 'abcdefghijklmnno');
let crypted = cipher.update(data, 'utf8', 'base64');
crypted += cipher.final('base64');
The results of the scripts differ, even though, in theory, the encryption should be the same.
An example of return is as follows:
For the php script: input -> ^y3Hk3JKGGgA output -> eTqD5Op389QS/TOoui5kAQ==
For the js script: input -> ^y3Hk3JKGGgA output -> HHfskOE1N+QxdGt9MTai5A==
The desired result is the PHP script, but I need to run the code in JS, can someone explain to me what I may be doing wrong?
I tried different ways to execute the createCipheriv method, but they all return the same result (different from what I need, which is the result obtained through the PHP script)
Thank you in advance.
Thank you guys for trying to help, indeed I posted the question lacking some informations (actually when the question was made I didn't have all the pieces of information I needed).
But posting here some facts and the solution encountered for my case.
The different results in the cases above only happen for the first PHP function ("encryptData"), responsible for encrypting small texts. The second, responsible for encrypting large texts (more than 16 bits) worked fine, both in PHP and JS scripts.
The solution I encountered was making the padding needed for the AES-256 algorithm by myself. The padding function provided by the Crypto lib didn't work, at least for my case.
So I disabled the padding in my cypher class and make sure that the data sent to be encrypted was padded correctly until the length is multiple of 16. The end's code is below.
encryptWithAES256(data) {
// added padding until data length is multiple of 16
let paddedData = data;
while (paddedData.length % 16 !== 0) {
paddedData += '\0';
}
// ciphers data
const cipher = crypto.createCipheriv('aes-256-cbc', encodeKey, IV);
cipher.setAutoPadding(false);
let crypted = cipher.update(paddedData, 'utf8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
I'm having an issue encrypting my string in C++ and then decrypting in PHP. On the C++ side, I think everything is going fine. Below is my code for the C++ side.
unsigned char inbuffer[1024];
unsigned char outbuffer[1024];
unsigned char oneKey[] = "abc";
AES_KEY key;
AES_set_encrypt_key(oneKey, 128, &key);
string straa("hello world\n");
memcpy((char*)inbuffer, straa.c_str(), 13);
AES_encrypt(inbuffer, encryptedbuffer, &key);
LPCSTR pszSource = (LPCSTR)encryptedbuffer;
DWORD nDestinationSize;
if (CryptBinaryToString(reinterpret_cast<const BYTE*> (pszSource), strlen(pszSource), CRYPT_STRING_BASE64, nullptr, &nDestinationSize))
{
LPTSTR pszDestination = static_cast<LPTSTR> (HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, nDestinationSize * sizeof(TCHAR)));
if (pszDestination)
{
if (CryptBinaryToString(reinterpret_cast<const BYTE*> (pszSource), strlen(pszSource), CRYPT_STRING_BASE64, pszDestination, &nDestinationSize))
{
printf("OUT: %s", pszDestination); // Base64 encoded output of encrypted string
}
}
}
This is my PHP code:
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('AES-256-CBC'));
$out = openssl_decrypt(base64_decode("G6g0f/K7EzAw8fxn9BTFzw=="), 'AES-256-CBC', "abc", OPENSSL_RAW_DATA, $iv);
echo $out;
There is no output from the PHP code.
The C++ code would output the following:
OUT: G6g0f/K7EzAw8fxn9BTFzw==
CBC mode, the mode used in the PHP code, does require an iv and it must be the same as the one used for encryption, also the modes must be the same for both encryption and decryption. For CBC mode you need to supply a block sized iv (16-bytes for AES). The encryption key also should to be the correct size.
The iv is not explicitly set in the C++ code and the mode and padding use the default, it is always better to expel;icitly specify all parameters. The iv should be random for each encryption.
You might consider RNCryptor, it is multi language and platform and handles all the details.
I was hoping someone had already implemented this in golang as I am far from even good at cryptography. However in porting a project from php to golang I have run into an issue with porting the openssl_encrypt method found here. I have also dug into the source code a little with no avail.
Here is the method I have implemented in golang. which gives me the output
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5
Here is the output I need when using php.
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH
And here is the function I used to generate it with php.
$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";
echo openssl_encrypt($data, $method, $password, $options, $iv);
To me it looks like it is very close and I must be missing something obvious.
You were very close, but you had the padding wrong. According to this answer (and the PHP docs), PHP uses the default OpenSSL padding behavior, which is to use the required number of padding bytes as the padding byte value.
The only change I made was:
copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))
You can see the full updated code here.
Others beat me to the answer while I was playing with it, but I have a "better" fixed version of your example code that also takes into account that padding is always required (at least to emulate what the php code does).
It also shows the openssl command line that you'd use to do the same thing, and if available runs it (of course the playground won't).
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
const input = "This is some text I want to encrypt"
fmt.Println(opensslCommand(input))
fmt.Println(aesCBCenctypt(input))
}
func aesCBCenctypt(input string) string {
// Of course real IVs should be from crypto/rand
iv := []byte("MMMMMMMMMMMMMMMM")
// And real keys should be from something like PBKDF2, RFC 2898.
// E.g. use golang.org/x/crypto/pbkdf2 to turn a
// "passphrase" into a key.
key := []byte("This is a really long key and su")
// Make sure the block size is a multiple of aes.BlockSize
// Pad to aes.BlockSize using the pad length as the padding
// byte. If we would otherwise need no padding we instead
// pad an entire extra block.
pad := (aes.BlockSize - len(input)%aes.BlockSize)
if pad == 0 {
pad = aes.BlockSize
}
data := make([]byte, len(input)+pad)
copy(data, input)
for i := len(input); i < len(input)+pad; i++ {
data[i] = byte(pad)
}
cb, err := aes.NewCipher(key)
if err != nil {
log.Fatalln("error NewCipher():", err)
}
mode := cipher.NewCBCEncrypter(cb, iv)
mode.CryptBlocks(data, data)
return base64.StdEncoding.EncodeToString(data)
}
// Just for comparison, don't do this for real!
func opensslCommand(input string) string {
iv := []byte("MMMMMMMMMMMMMMMM")
key := []byte("This is a really long key and su")
args := []string{"enc", "-aes-256-cbc", "-base64"}
// "-nosalt", "-nopad"
args = append(args, "-iv", fmt.Sprintf("%X", iv))
args = append(args, "-K", fmt.Sprintf("%X", key))
cmd := exec.Command("openssl", args...)
// Show how you could do this via the command line:
fmt.Println("Command:", strings.Join(cmd.Args, " "))
cmd.Stdin = strings.NewReader(input)
result, err := cmd.CombinedOutput()
if err != nil {
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
// openssl not available
return err.Error() // XXX
}
// some other error, show it and the (error?) output and die
fmt.Println("cmd error:", err)
log.Fatalf("result %q", result)
}
// Strip trailing '\n' and return it.
if n := len(result) - 1; result[n] == '\n' {
result = result[:n]
}
return string(result)
}
Playground