I'm trying to decrypt in c#, using bouncycastle, the result of the following php code:
<?php
$plaintext = 'The quick brown fox jumps over the lazy dog';
$cipher = "AES-128-CTR";
$key = "F5UgsDQddWGdgjddJtNgg6xE3V9uwaCR";
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = '2782614358578542';
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
echo $ciphertext."\n";
}
?>
My c# code is as follows:
public string Decrypt(string toDecrypt, string keyStr, string ivStr)
{
byte[] inputBytes = Convert.FromBase64String(toDecrypt);
byte[] keyBytes = Encoding.UTF8.GetBytes(keyStr);
byte[] iv = Encoding.UTF8.GetBytes(ivStr);
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/PKCS7PADDING");
cipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", keyBytes), iv));
byte[] decryptedBytes = cipher.DoFinal(inputBytes);
return Encoding.UTF8.GetString(decryptedBytes);
}
The php code returns
7/q61uOzeC4iycFIMrjvh01zvjOuCtnX8eWob8MAA5kIfMOIx915ctBIyw==
But when I make in C# the following call
Decrypt("7/q61uOzeC4iycFIMrjvh01zvjOuCtnX8eWob8MAA5kIfMOIx915ctBIyw==", "F5UgsDQddWGdgjddJtNgg6xE3V9uwaCR", "2782614358578542");
I get the following gibberish:
���yF�l����c:�-��7K�(�,�X�.[�W"�ܜ��J�
Which makes me think that I'm missing or doing something wrong with the encoding but I can't seem to figure it out.
AES-128 uses a 16 bytes key. In the PHP code, however, a 32 bytes key is used, which PHP implicitly truncates by considering only the first 16 bytes.
So the fix is to shorten the key in the C# code accordingly, i.e. use F5UgsDQddWGdgjdd.
Alternatively, change the AES variant in the PHP code to AES-256 (aes-256-ctr), which results in the ciphertext Tw05j9QDfaBK7zbyt9jine8xWqnzNB2Pim7rtv7gDba2TsE7ejvvjP5YKA==.
Additionally, in the C# code, apply AES/CTR/NoPadding, since CTR is a stream cipher mode that does not require padding, which is why padding is implicitly disabled in the PHP code.
Note that for security reasons, key/IV pairs must not be reused, especially for CTR. Therefore, for a fixed key, a static IV must not be applied, but a random one (which is passed to the decrypting side together with the ciphertext, usually concatenated).
The mcrypt-extension is deprecated will be removed in PHP 7.2 according to the comment posted here. So I am looking for an alternative way to encrypt passwords.
Right now I am using something like
mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($key, true), $string, MCRYPT_MODE_CBC, $iv)
I need your opinion for the best/strongest way to encrypt passwords, the encrypted password should of course supported by PHP 7.xx and should also be decryptable because my customers do want to have an option to 'recover' their passwords without generating a new one.
It's best practice to hash passwords so they are not decryptable. This makes things slightly more difficult for attackers that may have gained access to your database or files.
If you must encrypt your data and have it decryptable, a guide to secure encryption/decryption is available at https://paragonie.com/white-paper/2015-secure-php-data-encryption. To summarize that link:
Use Libsodium - A PHP extension
If you can't use Libsodium, use defuse/php-encryption - Straight PHP code
If you can't use Libsodium or defuse/php-encryption, use OpenSSL - A lot of servers will already have this installed. If not, it can be compiled with --with-openssl[=DIR]
As suggested by #rqLizard, you can use openssl_encrypt/openssl_decrypt PHP functions instead which provides a much
better alternative to implement AES (The Advanced Encryption Standard) also known as Rijndael encryption.
As per the following Scott's comment at php.net:
If you're writing code to encrypt/encrypt data in 2015, you should use openssl_encrypt() and openssl_decrypt(). The underlying library (libmcrypt) has been abandoned since 2007, and performs far worse than OpenSSL (which leverages AES-NI on modern processors and is cache-timing safe).
Also, MCRYPT_RIJNDAEL_256 is not AES-256, it's a different variant of the Rijndael block cipher. If you want AES-256 in mcrypt, you have to use MCRYPT_RIJNDAEL_128 with a 32-byte key. OpenSSL makes it more obvious which mode you are using (i.e. aes-128-cbc vs aes-256-ctr).
OpenSSL also uses PKCS7 padding with CBC mode rather than mcrypt's NULL byte padding. Thus, mcrypt is more likely to make your code vulnerable to padding oracle attacks than OpenSSL.
Finally, if you are not authenticating your ciphertexts (Encrypt Then MAC), you're doing it wrong.
Further reading:
Using Encryption and Authentication Correctly (for PHP developers).
If You're Typing the Word MCRYPT Into Your PHP Code, You're Doing It Wrong.
Code examples
Example #1
AES Authenticated Encryption in GCM mode example for PHP 7.1+
<?php
//$key should have been previously generated in a cryptographically safe way, like openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$cipher = "aes-128-gcm";
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
//store $cipher, $iv, and $tag for decryption later
$original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
echo $original_plaintext."\n";
}
?>
Example #2
AES Authenticated Encryption example for PHP 5.6+
<?php
//$key previously generated safely, ie: openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
//decrypt later....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac))//PHP 5.6+ timing attack safe comparison
{
echo $original_plaintext."\n";
}
?>
Example #3
Based on above examples, I've changed the following code which aims at encrypting user's session id:
class Session {
/**
* Encrypts the session ID and returns it as a base 64 encoded string.
*
* #param $session_id
* #return string
*/
public function encrypt($session_id) {
// Get the MD5 hash salt as a key.
$key = $this->_getSalt();
// For an easy iv, MD5 the salt again.
$iv = $this->_getIv();
// Encrypt the session ID.
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $session_id, MCRYPT_MODE_CBC, $iv);
// Base 64 encode the encrypted session ID.
$encryptedSessionId = base64_encode($encrypt);
// Return it.
return $encryptedSessionId;
}
/**
* Decrypts a base 64 encoded encrypted session ID back to its original form.
*
* #param $encryptedSessionId
* #return string
*/
public function decrypt($encryptedSessionId) {
// Get the MD5 hash salt as a key.
$key = $this->_getSalt();
// For an easy iv, MD5 the salt again.
$iv = $this->_getIv();
// Decode the encrypted session ID from base 64.
$decoded = base64_decode($encryptedSessionId);
// Decrypt the string.
$decryptedSessionId = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decoded, MCRYPT_MODE_CBC, $iv);
// Trim the whitespace from the end.
$session_id = rtrim($decryptedSessionId, "\0");
// Return it.
return $session_id;
}
public function _getIv() {
return md5($this->_getSalt());
}
public function _getSalt() {
return md5($this->drupal->drupalGetHashSalt());
}
}
into:
class Session {
const SESS_CIPHER = 'aes-128-cbc';
/**
* Encrypts the session ID and returns it as a base 64 encoded string.
*
* #param $session_id
* #return string
*/
public function encrypt($session_id) {
// Get the MD5 hash salt as a key.
$key = $this->_getSalt();
// For an easy iv, MD5 the salt again.
$iv = $this->_getIv();
// Encrypt the session ID.
$ciphertext = openssl_encrypt($session_id, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
// Base 64 encode the encrypted session ID.
$encryptedSessionId = base64_encode($ciphertext);
// Return it.
return $encryptedSessionId;
}
/**
* Decrypts a base 64 encoded encrypted session ID back to its original form.
*
* #param $encryptedSessionId
* #return string
*/
public function decrypt($encryptedSessionId) {
// Get the Drupal hash salt as a key.
$key = $this->_getSalt();
// Get the iv.
$iv = $this->_getIv();
// Decode the encrypted session ID from base 64.
$decoded = base64_decode($encryptedSessionId, TRUE);
// Decrypt the string.
$decryptedSessionId = openssl_decrypt($decoded, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
// Trim the whitespace from the end.
$session_id = rtrim($decryptedSessionId, '\0');
// Return it.
return $session_id;
}
public function _getIv() {
$ivlen = openssl_cipher_iv_length(self::SESS_CIPHER);
return substr(md5($this->_getSalt()), 0, $ivlen);
}
public function _getSalt() {
return $this->drupal->drupalGetHashSalt();
}
}
To clarify, above change is not a true conversion since the two encryption uses a different block size and a different encrypted data. Additionally, the default padding is different, MCRYPT_RIJNDAEL only supports non-standard null padding. #zaph
Additional notes (from the #zaph's comments):
Rijndael 128 (MCRYPT_RIJNDAEL_128) is equivalent to AES, however Rijndael 256 (MCRYPT_RIJNDAEL_256) is not AES-256 as the 256 specifies a block size of 256-bits, whereas AES has only one block size: 128-bits. So basically Rijndael with a block size of 256-bits (MCRYPT_RIJNDAEL_256) has been mistakenly named due to the choices by the mcrypt developers. #zaph
Rijndael with a block size of 256 may be less secure than with a block size of 128-bits because the latter has had much more reviews and uses. Secondly, interoperability is hindered in that while AES is generally available, where Rijndael with a block size of 256-bits is not.
Encryption with different block sizes for Rijndael produces different encrypted data.
For example, MCRYPT_RIJNDAEL_256 (not equivalent to AES-256) defines a different variant of the Rijndael block cipher with size of 256-bits and a key size based on the passed in key, where aes-256-cbc is Rijndael with a block size of 128-bits with a key size of 256-bits. Therefore they're using different block sizes which produces entirely different encrypted data as mcrypt uses the number to specify the block size, where OpenSSL used the number to specify the key size (AES only has one block size of 128-bits). So basically AES is Rijndael with a block size of 128-bits and key sizes of 128, 192 and 256 bits. Therefore it's better to use AES, which is called Rijndael 128 in OpenSSL.
As detailed by other answers here, the best solution I found is using OpenSSL. It is built into PHP and you don't need any external library. Here are simple examples:
To encrypt:
function encrypt($key, $payload) {
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv);
return base64_encode($encrypted . '::' . $iv);
}
To decrypt:
function decrypt($key, $garble) {
list($encrypted_data, $iv) = explode('::', base64_decode($garble), 2);
return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}
Reference link: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/
Pure-PHP implementation of Rijndael exists with phpseclib available as composer package and works on PHP 7.3 (tested by me).
There's a page on the phpseclib docs, which generates sample code after you input the basic variables (cipher, mode, key size, bit size). It outputs the following for Rijndael, ECB, 256, 256:
a code with mycrypt
$decoded = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ENCRYPT_KEY, $term, MCRYPT_MODE_ECB);
works like this with the library
$rijndael = new \phpseclib\Crypt\Rijndael(\phpseclib\Crypt\Rijndael::MODE_ECB);
$rijndael->setKey(ENCRYPT_KEY);
$rijndael->setKeyLength(256);
$rijndael->disablePadding();
$rijndael->setBlockLength(256);
$decoded = $rijndael->decrypt($term);
* $term was base64_decoded
You can use phpseclib pollyfill package. You can not use open ssl or libsodium for encrypt/decrypt with rijndael 256.
Another issue, you don't need replacement any code.
You should use OpenSSL over mcrypt as it's actively developed and maintained. It provides better security, maintainability and portability. Secondly it performs AES encryption/decryption much faster. It uses PKCS7 padding by default, but you can specify OPENSSL_ZERO_PADDING if you need it. To use with a 32-byte binary key, you can specify aes-256-cbc which is much obvious than MCRYPT_RIJNDAEL_128.
Here is the code example using Mcrypt:
Unauthenticated AES-256-CBC encryption library written in Mcrypt with PKCS7 padding.
/**
* This library is unsafe because it does not MAC after encrypting
*/
class UnsafeMcryptAES
{
const CIPHER = MCRYPT_RIJNDAEL_128;
public static function encrypt($message, $key)
{
if (mb_strlen($key, '8bit') !== 32) {
throw new Exception("Needs a 256-bit key!");
}
$ivsize = mcrypt_get_iv_size(self::CIPHER);
$iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_URANDOM);
// Add PKCS7 Padding
$block = mcrypt_get_block_size(self::CIPHER);
$pad = $block - (mb_strlen($message, '8bit') % $block, '8bit');
$message .= str_repeat(chr($pad), $pad);
$ciphertext = mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
$key,
$message,
MCRYPT_MODE_CBC,
$iv
);
return $iv . $ciphertext;
}
public static function decrypt($message, $key)
{
if (mb_strlen($key, '8bit') !== 32) {
throw new Exception("Needs a 256-bit key!");
}
$ivsize = mcrypt_get_iv_size(self::CIPHER);
$iv = mb_substr($message, 0, $ivsize, '8bit');
$ciphertext = mb_substr($message, $ivsize, null, '8bit');
$plaintext = mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$key,
$ciphertext,
MCRYPT_MODE_CBC,
$iv
);
$len = mb_strlen($plaintext, '8bit');
$pad = ord($plaintext[$len - 1]);
if ($pad <= 0 || $pad > $block) {
// Padding error!
return false;
}
return mb_substr($plaintext, 0, $len - $pad, '8bit');
}
}
And here is the version written using OpenSSL:
/**
* This library is unsafe because it does not MAC after encrypting
*/
class UnsafeOpensslAES
{
const METHOD = 'aes-256-cbc';
public static function encrypt($message, $key)
{
if (mb_strlen($key, '8bit') !== 32) {
throw new Exception("Needs a 256-bit key!");
}
$ivsize = openssl_cipher_iv_length(self::METHOD);
$iv = openssl_random_pseudo_bytes($ivsize);
$ciphertext = openssl_encrypt(
$message,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$iv
);
return $iv . $ciphertext;
}
public static function decrypt($message, $key)
{
if (mb_strlen($key, '8bit') !== 32) {
throw new Exception("Needs a 256-bit key!");
}
$ivsize = openssl_cipher_iv_length(self::METHOD);
$iv = mb_substr($message, 0, $ivsize, '8bit');
$ciphertext = mb_substr($message, $ivsize, null, '8bit');
return openssl_decrypt(
$ciphertext,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$iv
);
}
}
Source: If You're Typing the Word MCRYPT Into Your PHP Code, You're Doing It Wrong.
I am using this on PHP 7.2.x, it's working fine for me:
public function make_hash($userStr){
try{
/**
* Used and tested on PHP 7.2x, Salt has been removed manually, it is now added by PHP
*/
return password_hash($userStr, PASSWORD_BCRYPT);
}catch(Exception $exc){
$this->tempVar = $exc->getMessage();
return false;
}
}
and then authenticate the hash with the following function:
public function varify_user($userStr,$hash){
try{
if (password_verify($userStr, $hash)) {
return true;
}
else {
return false;
}
}catch(Exception $exc){
$this->tempVar = $exc->getMessage();
return false;
}
}
Example:
//create hash from user string
$user_password = $obj->make_hash2($user_key);
and to authenticate this hash use the following code:
if($obj->varify_user($key, $user_key)){
//this is correct, you can proceed with
}
That's all.
As pointed out, you should not be storing your users' passwords in a format that is decryptable. Reversable encryption provides an easy route for hackers to find out your users' passwords, which extends to putting your users' accounts at other sites at risk should they use the same password there.
PHP provides a pair of powerful functions for random-salted, one-way hash encryption — password_hash() and password_verify(). Because the hash is automatically random-salted, there is no way for hackers to utilize precompiled tables of password hashes to reverse-engineer the password. Set the PASSWORD_DEFAULT option and future versions of PHP will automatically use stronger algorithms to generate password hashes without you having to update your code.
I was able to translate my Crypto object
Get a copy of php with mcrypt to decrypt the old data. I went to http://php.net/get/php-7.1.12.tar.gz/from/a/mirror, compiled it, then added the ext/mcrypt extension (configure;make;make install). I think I had to add the extenstion=mcrypt.so line to the php.ini as well. A series of scripts to build intermediate versions of the data with all data unencrypted.
Build a public and private key for openssl
openssl genrsa -des3 -out pkey.pem 2048
(set a password)
openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
To Encrypt (using public key) use openssl_seal. From what I've read, openssl_encrypt using an RSA key is limited to 11 bytes less than the key length (See http://php.net/manual/en/function.openssl-public-encrypt.php comment by Thomas Horsten)
$pubKey = openssl_get_publickey(file_get_contents('./pkey-pub.pem'));
openssl_seal($pwd, $sealed, $ekeys, [ $pubKey ]);
$encryptedPassword = base64_encode($sealed);
$key = base64_encode($ekeys[0]);
You could probably store the raw binary.
To Decrypt (using private key)
$passphrase="passphrase here";
$privKey = openssl_get_privatekey(file_get_contents('./pkey.pem'), $passphrase);
// I base64_decode() from my db columns
openssl_open($encryptedPassword, $plain, $key, $privKey);
echo "<h3>Password=$plain</h3>";
P.S. You can't encrypt the empty string ("")
P.P.S. This is for a password database not for user validation.
You should use openssl_encrypt() function.
I am building a iPhone app which uses a c# web service. My iPhone app takes in some data and encrypts it and passes it to the web service. How do I decrypt the data in C#?
My iPhone app contains the following code:
NSString *pString = #"Some string to be encoded";
NSString *key = #"My encryption key";
NSData *pData = [pString dataUsingEncoding:NSUTF8StringEncoding];
pData = [pData AES256EncryptWithKey:key];
NSString *pID = [pData base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
EDIT: The data is already stored in the web service so I can't readily change the encryption approach. The C# application is not on the server so there is no possibility of compromising the key.
I have tried the following C# code to decrypt the data:
static string DecryptString(string encryptedText, string key)
{
byte[] encryptedString = Convert.FromBase64String(encryptedText);
byte[] encryptionKey = Encoding.UTF8.GetBytes(key.Substring(0, 32));
using (var provider = new AesCryptoServiceProvider())
{
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
However, I get the following exception:
System.Security.Cryptography.CryptographicException was unhandled
HResult=-2146233296 Message=Padding is invalid and cannot be
removed.
The encryptedText received by DecryptString is 80 bytes in length.
The sample ObjC code uses by default CBC modem, PKCS#7 padding and a default iv of 16 0x00 bytes.
The C# also uses CBC mode and PKCS#7 padding. The decryption code expects a 16-byte iv pre-pended to the encrypted data and that does not exist.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
This needs to be changed so that iv is set to an array of 16 0x00 bytes and the ms.Read(iv, 0, 16) statement needs to be deleted so the decrypt function gets all of the encrypted data.
Notes:
Using a devault anything in encryption is a bad idea, always provide the correect length data.
Authentication of the encrypted data needs should be added so that it can be determined if there an incorrect key or the data has been tampered with.
There really should be a version number and a random IV used and prepended to the encrypted so you should really consider correcting this. This demonstrates why a version number generally needs to be provided and used.
RNCryptor covers the above issues.
The handling of the encryption key also needs to be considered so that is is as secure as necessary.
You need to first decode the base-64 encoded string to a byte[] - see Convert.FromBase64String(). Then you need to use the Aes class to decrypt it - there's an example on its documentation page.
I'm trying to convert this C# code to Python (2.5, GAE). The problem is that the encrypted string from the python script is different each time the encryption (on the same string) is run.
string Encrypt(string textToEncrypt, string passphrase)
{
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = 128;
rijndaelCipher.BlockSize = 128;
byte[] pwdBytes = Encoding.UTF8.GetBytes(passphrase);
byte[] keyBytes = new byte[16];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
{
len = keyBytes.Length;
}
Array.Copy(pwdBytes, keyBytes, len);
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = new byte[16];
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
return Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
}
Python code: (PKCS7Encoder: http://japrogbits.blogspot.com/2011/02/using-encrypted-data-between-python-and.html)
from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder
#declared outside of all functions
key = '####'
mode = AES.MODE_CBC
iv = '\x00' * 16
encryptor = AES.new(key, mode, iv)
encoder = PKCS7Encoder()
def function(self):
text = self.request.get('passwordTextBox')
pad_text = encoder.encode(text)
cipher = encryptor.encrypt(pad_text)
enc_cipher = base64.b64encode(cipher)
The C# code is inherited. Python code must be encrypted and decrypted the same way so that the C# code can decode the value correctly.
Note: I am a noob at python :)
Edit: sorry. should have made the distinction that there was a function being called.
Thanks!
Your C# code is invalid.
The Encrypt function takes in the passphrase as string passphrase but then tries to reference it in this line byte[] pwdBytes = Encoding.UTF8.GetBytes(key);
Change key to passphrase.
The two functions now produce identical results for me:
Python
secret_text = 'The rooster crows at midnight!'
key = 'A16ByteKey......'
mode = AES.MODE_CBC
iv = '\x00' * 16
encoder = PKCS7Encoder()
padded_text = encoder.encode(secret_text)
e = AES.new(key, mode, iv)
cipher_text = e.encrypt(padded_text)
print(base64.b64encode(cipher_text))
# e = AES.new(key, mode, iv)
# cipher_text = e.encrypt(padded_text)
# print(base64.b64encode(cipher_text))
C# (with the typo fix mentioned above)
Console.WriteLine(Encrypt("The rooster crows at midnight!", "A16ByteKey......"));
Python Result
XAW5KXVbItrc3WF0xW175UJoiAfonuf+s54w2iEs+7A=
C# Result
XAW5KXVbItrc3WF0xW175UJoiAfonuf+s54w2iEs+7A=
I suspect you're re-using 'e' in your python code multiple times. If you uncomment the last two lines of my python script, you'll see the output is now different. But if you uncomment the last three lines, you'll see the output is the same. As Foon said, this is due to how CBC works.
CBC (Cipher-block chaining) works when encrypting a sequence of bytes in blocks. The first block is encrypted by incorporating the IV with the first bytes of your plaintext ("The rooster..."). The second block uses the result of that first operation instead of the IV.
When you call e.encrypt() a second time (e.g. by uncommmenting the last two lines of the python script) you pick up where you left off. Instead of using the IV when encrypting the first block, it will use the output of the last encrypted block. This is why the results look different. By uncommening the last three lines of the python script you initialize a new encryptor which will use the IV for its first block, causing you to get identical results.
changed python code to:
from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder
#declared outside of all functions
key = '####'
mode = AES.MODE_CBC
iv = '\x00' * 16
encoder = PKCS7Encoder()
def function(self):
encryptor = AES.new(key, mode, iv)**
text = self.request.get('passwordTextBox')
pad_text = encoder.encode(text)
cipher = encryptor.encrypt(pad_text)
enc_cipher = base64.b64encode(cipher)
in case anyone reaches this page via google
This esotic PKCS7 encoder is anything else then a function that pads with a static lenght.
So I implemented it with a very chip of code
#!/usr/bin/env python
from Crypto.Cipher import AES
import base64
# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 16
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
# PKCS7 method
PADDING = '\x06'
mode = AES.MODE_CBC
iv = '\x08' * 16 # static vector: dangerous for security. This could be changed periodically
#
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
def CryptIt(password, secret):
cipher = AES.new(secret, mode, iv)
encoded = EncodeAES(cipher, password)
return encoded
def DeCryptIt(encoded, secret):
cipher = AES.new(secret, mode, iv)
decoded = DecodeAES(cipher, encoded)
return decoded
I hope that this could help.
Cheers
Microsoft's implementation of PKCS7 is a bit different than Python's.
This article helped me with this problem:
http://japrogbits.blogspot.com/2011/02/using-encrypted-data-between-python-and.html
His code for pkcs7 encoding and decoding is on github here:
https://github.com/janglin/crypto-pkcs7-example
With that PKCS7 library, this code worked for me:
from Crypto.Cipher import AES
aes = AES.new(shared_key, AES.MODE_CBC, IV)
aes.encrypt(PKCS7Encoder().encode(data))
I've spent a couple hours now trying to figure this out, but I just can't get it to work. I've got a C# encryption routine that I need to match in php. I can't change the C# version, that's not an option (3rd party is firm on this).
Here's the C# code:
//In C#
// Console.WriteLine(ApiEncode("testing", "56dsfkj3kj23asdf83kseegflkj43458afdl"));
// Results in:
// XvHbR/CsLTo=
public static string ApiEncode(string data, string secret)
{
byte[] clear;
var encoding = new UTF8Encoding();
var md5 = new MD5CryptoServiceProvider();
byte[] key = md5.ComputeHash(encoding.GetBytes(secret));
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.Key = key;
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.PKCS7;
byte[] input = encoding.GetBytes(data);
try { clear = des.CreateEncryptor().TransformFinalBlock(input, 0, input.Length); }
finally
{
des.Clear();
md5.Clear();
}
return Convert.ToBase64String(clear);
}
Here's the best of what I've come up with in PHP:
//In PHP
// echo apiEncode("testing", "56dsfkj3kj23asdf83kseegflkj43458afdl");
// Results in:
// 5aqvY6q1T54=
function apiEncode($data, $secret)
{
//Generate a key from a hash
$key = md5(utf8_encode($secret), true);
//Create init vector
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ecb), MCRYPT_RAND);
//Pad for PKCS7
$blockSize = mcrypt_get_block_size('tripledes', 'ecb');
$len = strlen($data);
$pad = $blockSize - ($len % $blockSize);
$data .= str_repeat(chr($pad), $pad);
//Encrypt data
$encData = mcrypt_encrypt('tripledes', $key, $data, 'ecb'); //, $iv);
return base64_encode($encData);
}
To the best of my knowledge, I'm handling the PKCS7 padding properly on the PHP side. I'm not sure what else to try.
One thing to note, the C# is happening on windows, and the PHP on linux, not sure that should make a difference.
The padding length in your PHP version is based on the length of the password. This is incorrect. It should be based on the length of your message instead.
Try replacing strlen($password) with strlen($data).
The second problem is that the mcrypt library requires 24-byte keys. Triple DES applies regular DES three times, so you can call the 8-byte key used in each round of DES K1, K2, and K3. There are different ways to choose these keys. The most secure is to choose three distinct keys. Another way is to set K3 equal to K1. The least secure method (equivalent to DES) is to make K1 = K2 = K3.
Most libraries are "smart" enough to interpret a 16-byte 3DES key as the second option above: K3 = K1. The .NET implementation is doing this for you, but the mcrypt library is not; instead, it's setting K3 = 0. You'll need to fix this yourself, and pass mcrypt a 24-byte key.
After computing the MD5 hash, take the first 8 bytes of $key, and append them to the end of $key, so that you have a 24-byte value to pass to mcrypt_encrypt().
I found a solution, check this link, may help you. http://sanity-free.com/131/triple_des_between_php_and_csharp.html
And here is the decrypt function just in case:
public static string Decrypt(string cypherString)
{
byte[] key = Encoding.ASCII.GetBytes("icatalogDR0wSS#P6660juht");
byte[] iv = Encoding.ASCII.GetBytes("iCatalog");
byte[] data = Convert.FromBase64String(cypherString);
byte[] enc = new byte[0];
TripleDES tdes = TripleDES.Create();
tdes.IV = iv;
tdes.Key = key;
tdes.Mode = CipherMode.CBC;
tdes.Padding = PaddingMode.Zeros;
ICryptoTransform ict = tdes.CreateDecryptor();
enc = ict.TransformFinalBlock(data, 0, data.Length);
return UTF8Encoding.UTF8.GetString(enc, 0, enc.Length);
}
Take a look at encoding.getBytes, you need the secret key Bytes from UTF8...
It appears the C# version does not set the IV. This could be an issue if you dont know what it is because msdn says:
The IV property is automatically set to a new random value whenever you create a new instance of one of the SymmetricAlgorithm classes or when you manually call the GenerateIV method.
It looks like in the PHP version, you are using an IV. You could try not supplying the IV and hope the C# version also uses zeros.
Edit: Looks like for ECB, the IV is ignored.
You might also need to encoding the key like in the C# version using utf8-encode