2011-05-18 120 views
9

在的node.js,我使用構建在功能上類似的數據進行加密:在Node.js中進行AES加密在PHP中進行解密。失敗。

var text = "Yes"; 
var password = "123456"; 
var encrypt = crypto.createCipher('aes-256-cbc', password); 
var encryptOutput1 = encrypt.update(text, 'base64', 'base64'); 
var encryptOutput2 = encrypt.final('base64'); 
var encryptedText = encryptOutput1 + encryptOutput2; 

輸出(加密文本)是:OnNINwXf6U8XmlgKJj48iA ==

然後,我使用解密在PHP:

$encrypted = 'OnNINwXf6U8XmlgKJj48iA=='; 
(or $encrypted = base64_decode('OnNINwXf6U8XmlgKJj48iA==') ); 
$dtext2 = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC); 
echo "Decrypted: $dtext2"; 

我會得到一些有趣的人物,我不能解密它。我嘗試了/沒有base64_decode或MCRYPT_RIJNDAEL_128 ..都失敗了。

然後我檢查PHP中的加密方式,它看起來與node.js的輸出非常不同。

$text = "Yes"; 
    $key = "123456"; 


    $eText = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC); 
    echo "Encrypted: $eText \n"; 
    echo "base64: " . base64_encode($eText) . " \n"; 

    $dtext1 = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $eText, MCRYPT_MODE_CBC); 
    echo "Decrypted: $dtext1 \n\n"; 

它可以加密和解密。和加密的數據爲:njCE/fk3pLD1/JfiQuyVa6w5H + QB/utBIT3m7LAcetM =

其是從node.js的輸出非常不同請告知如何可以加密和的node.js之間解密& PHP。謝謝。 :)這裏


@Mel是我在PHP中:

$text = "Yes"; 

$key = "32BytesLongKey56ABCDEF"; 
$iv = "sixteenbyteslong"; 

/* Open the cipher */ 
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 

/* Intialize encryption */ 
mcrypt_generic_init($td, $key, $iv); 

/* Encrypt data */ 
$eText = mcrypt_generic($td, $text); 

echo "Encrypted Data: $eText \n"; 
echo "base64: " . base64_encode($eText) . " \n"; 

/* Terminate encryption handler */ 
mcrypt_generic_deinit($td); 

/* Initialize encryption module for decryption */ 
mcrypt_generic_init($td, $key, $iv); 

/* Decrypt encrypted string */ 
$dText = mdecrypt_generic($td, $eText); 

/* Terminate decryption handle and close module */ 
mcrypt_generic_deinit($td); 
mcrypt_module_close($td); 

/* Show string */ 
echo trim($dText) . "\n"; 

但是,它仍然無法正常工作。

PHP中的加密的基部64是:80022AGM4/4qQtiGU5oJDQ == 加密的底座64中的NodeJS是:EoYRm5SCK7EPe847CwkffQ ==

因此,我不能解密一個的NodeJS在PHP。

我不知道是不是因爲nodejs不需要$ iv?

回答

1

AES是rijndael的固定大小16字節四。詳情 here 不能用於解密。 更重要的是,我可以使用OpenSSL的無法解密你的字符串,要麼是:

% openssl aes-256-cbc -d -in dec.txt -a 
enter aes-256-cbc decryption password: 
bad magic number 

或者使用PHP:

$encrypted = 'OnNINwXf6U8XmlgKJj48iA=='; 
$text = 'Yes'; 
$pw = '123456'; 
$decrypted = @openssl_decrypt($encrypted, 'aes-256-cbc', $pw); 
var_dump($decrypted); 
var_dump(@openssl_encrypt($text, 'aes-256-cbc', $pw, FALSE, $pw)); 
var_dump(@openssl_encrypt($text, 'aes-256-cbc', $pw)); 

輸出:

bool(false) 
string(24) "xrYdu2UyJfxhhEHAKWv30g==" 
string(24) "findrYaZVpZWVhEgOEVQwQ==" 

如此看來,Node.js的是使用一些無證的功能來創建IV,我看不到在node.js中提供IV的方法。

+0

我必須保持密鑰32字節嗎? – murvinlai 2011-05-18 16:43:53

+0

它不起作用。我曾嘗試在node.js中使用AES-256進行加密。然後使用具有相同32字節密鑰的RJ-128進行解密,並且使用16字節的iv。仍然失敗。 – murvinlai 2011-05-18 17:45:41

+0

加密後的加密數據是base64編碼的。這就是爲什麼它有所作爲? – murvinlai 2011-05-18 19:16:21

2

我剛剛開始搞亂node.js,但我認爲你的問題與IV不匹配有關。嘗試這樣做,而不是執行以下操作:

var encrypt = crypto.createCipheriv('aes-256-cbc', password, /* password.createHash('md5').toHex()*/); 

PS:我不知道如何在node.js中的MD5哈希值,你就必須figure it out for yourself並相應地改變上面的代碼。

而在PHP:

$decrypt = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, md5($key)), "\0"); 

這應該確保這兩個實現使用相同的初始化向量。

我還建議你做如下修改:

  • 密碼:MD5(original_password)
  • IV = MD5(MD5(original_password))

這將確保PHP韓元不會拋出任何愚蠢的錯誤。請參閱Best way to use PHP to encrypt and decrypt passwords?

+0

謝謝..我會嘗試..但我沒有看到在crypt API中有一個稱爲crypto.createCipheriv的函數。是否來自其他npm包? – murvinlai 2011-05-18 19:09:35

+0

@murvinlai:似乎沒有記錄,但它在那裏:https://github.com/joyent/node/blob/9f0b1a9bc60f70b7c5c014743eb1edd69c36db76/lib/crypto.js#L118。 – 2011-05-18 19:13:50

16

遲了七個月,但我也爲此付出了努力,並找到了解決方案。顯然,PHP使用零字節填充輸入以使其大小成爲塊大小的倍數。例如,使用AES-128中,14字節的輸入「contrabassists」將與兩個零字節填充,這樣的:

"contrabassists\0\0" 

A N *塊大小字節輸入被單獨留下。

但是,標準節點加密函數使用不同的填充方案,稱爲PKCS5。 PKCS5不加零,但增加了填充的長度,所以再次使用AES-128,「contrabassists」將成爲:

"contrabassists\2\2" 

即使是N *塊大小字節輸入獲取PKCS5填充。否則,解碼後不可能刪除填充。然後輸入「spectroheliogram」將成爲:

"spectroheliogram\16\16\16\16\16\16\16\16\16\16\16\16\16\16\16\16" 

使PHP m_crypt加密,節點解密兼容的,你必須墊輸入自己:

$pad = $blocksize - (strlen($input) % $blocksize); 
$input = $input . str_repeat(chr($pad), $pad); 

其他的方式,你」必須讀取解碼數據的最後一個字節並自行切斷填充。

實施例的功能:(加入2012年1月14日)

在PHP中,該函數將返回AES-128加密,十六進制編碼可以由節點解密的數據:

function nodeEncrypt($data, $key, $iv) { 
    $blocksize = 16; // AES-128 
    $pad = $blocksize - (strlen($data) % $blocksize); 
    $data = $data . str_repeat(chr($pad), $pad); 
    return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv)); 
} 

在節點,下面就對數據進行解密:

function nodeDecrypt(data, key, iv) { 
    var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); 
    var chunks = [] 
    chunks.push(decipher.update(data.toString(),'hex','binary')) 
    chunks.push(decipher.final('binary')) 
    return chunks.join('') 
} 

我沒有做反向還,但它應該是簡單的,一旦你易懂填充方案。我沒有對關鍵/四代產生任何假設。

+0

謝謝我會嘗試一下..你有沒有任何例子? – murvinlai 2012-01-09 10:57:14

+0

我已經添加了一些應該開箱即用的示例函數 – Michilus 2012-01-14 17:47:39

+0

它非常有用非常感謝你 – Lupus 2013-05-23 07:50:56

0

Node.js正在用你的輸入密碼做一些魔法來派生一個密鑰和iv。很難看到這將如何在PHP中起作用,除非PHP執行完全相同的密鑰和iv派生魔術。

爲什麼不使用createCipheriv代替。使用基於密碼的密鑰派生函數從密碼創建密鑰。例如:

http://en.wikipedia.org/wiki/PBKDF2

這樣的功能是在Node.js的更新版本可用

http://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_pbkdf2_password_salt_iterations_keylen_callback

提供良好的IV爲好;你可以使用crypto.randomBytes創建一個。如果您控制關鍵和iv參數,那麼您將更容易確定是否可以將數據傳送到PHP。

你不能只散列密碼來生成一個iv。對於每個加密的消息,iv應該是不同的,否則它是無用的。

此外,你告訴Node.js你的輸入字符串「是」是Base64編碼,但我認爲它是真正的ASCII或UTF-8。

0

如果您遇到使用MCRYPT_RIJNDAEL_256的第三方庫,請注意,256指定了塊大小,而不是指定了密鑰大小。 AES使用固定的128位塊大小,openssl不實現更通用的Rijndael算法。爲了規避這個問題,我發佈了一個綁定到libmcrypt的模塊,就像PHP一樣。這是一個非常有限的用例,但它確保它將與256位塊大小的rijndael兼容。

如果你在PHP

使用該
mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $plaintext, MCRYPT_MODE_ECB); 
mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $ciphertext, MCRYPT_MODE_ECB); 

你可以做同樣的節點:

var rijndael = require('node-rijndael'); 

// straight through (must be buffers) 
rijndael.encrypt(plaintext, key); 
rijndael.decrypt(ciphertext, key); 

// or bound (can take a string for the key and an encoding) 
var rijn = rijndael(key); 
rijn.encrypt(plaintext); // gotta be a buffer again for implementation simplicity 
rijn.decrypt(ciphertext); 

node-rijndael on GitHub

node-rijndael on npm

0

我還另一實施in this other post如果它有助於其他人。

如果你確保你使用的32個字符長度「鍵/祕密」,並在PHP和節點16個字符長度的IV,和base64加密編碼,並在節點utf8消息編碼,那麼你不應該有任何問題填充模式的任何差異。

問候, 伊格納西奧

1

我發現了幾件事情,這可能是原因爲什麼PHP和Node.js的加密/解密是不一樣的。

PHP使用了MCRYPT_RIJNDAEL_256 algorythm。 AES 256基於MCRYPT_RIJNDAEL_256,但不一樣。 AES 256這是加密標準,但不是算法。

如果您嘗試使用標準簡單函數(例如PHP上的「mcrypt_encrypt」和「mcrypt_decrypt」)對某些內容進行加密,則無法看到所有步驟,並且您肯定無法知道爲什麼不能解密你所加密的內容。它可以與Node.js相同,因爲需要使用可逐步加密的函數來防止替換爲默認參數。

加密/解密,你需要知道的一些事情(設置):

encryption method (algorythm) 
encryption mode (CBF, ECB, CBC...) 
key to decryption 
key lenght 
initialisation vector lenght 

,並檢查它的兩側。它應該是一樣的。 還需要找到正確工作在雙方的正確組合「加密方法」+「加密模式」。

我的解決方案是RIJNDAEL_256 + ECB。 您應該安裝node-rijndael,因爲它確實使用RIJNDAEL_256。如果沒有 - 我的例子不會工作。

這裏是Node.js加密示例

在某些文件夾中安裝node-rijndael應該是兩個.js文件。

r256.js - 它是加密/解密的函數。我發現它here

var Rijndael = require('node-rijndael'); 

/** 
* Pad the string with the character such that the string length is a multiple 
* of the provided length. 
* 
* @param {string} string The input string. 
* @param {string} chr The character to pad with. 
* @param {number} length The base length to pad to. 
* @return {string} The padded string. 
*/ 
function rpad(string, chr, length) { 
    var extra = string.length % length; 
    if (extra === 0) 
    return string; 

    var pad_length = length - extra; 
    // doesn't need to be optimized because pad_length will never be large 
    while (--pad_length >= 0) { 
    string += chr; 
    } 
    return string; 
} 

/** 
* Remove all characters specified by the chr parameter from the end of the 
* string. 
* 
* @param {string} string The string to trim. 
* @param {string} chr The character to trim from the end of the string. 
* @return {string} The trimmed string. 
*/ 
function rtrim(string, chr) { 
    for (var i = string.length - 1; i >= 0; i--) 
    if (string[i] !== chr) 
     return string.slice(0, i + 1); 

    return ''; 
} 

/** 
* Encrypt the given plaintext with the base64 encoded key and initialization 
* vector. 
* 
* Null-pads the input plaintext. This means that if your input plaintext ends 
* with null characters, they will be lost in encryption. 
* 
* @param {string} plaintext The plain text for encryption. 
* @param {string} input_key Base64 encoded encryption key. 
* @param {string} input_iv Base64 encoded initialization vector. 
* @return {string} The base64 encoded cipher text. 
*/ 
function encrypt(plaintext, input_key, input_iv) { 
    var rijndael = new Rijndael(input_key, { 
    mode: Rijndael.MCRYPT_MODE_ECB, 
    encoding: 'base64', 
    iv: input_iv 
    }); 
console.log("Rijndael.blockSize", Rijndael.blockSize); 
    var padded = rpad(plaintext, '\0', Rijndael.blockSize); 

    return rijndael.encrypt(padded, 'binary', 'base64'); 
} 

/** 
* Decrypt the given ciphertext with the base64 encoded key and initialization 
* vector. 
* 
* Reverses any null-padding on the original plaintext. 
* 
* @param {string} ciphertext The base64 encoded ciphered text to decode. 
* @param {string} input_key Base64 encoded encryption key. 
* @param {string} input_iv Base64 encoded initialization vector. 
* @param {string} The decrypted plain text. 
*/ 
function decrypt(ciphertext, input_key, input_iv) { 
    var rijndael = new Rijndael(input_key, { 
    mode: Rijndael.MCRYPT_MODE_ECB, 
    encoding: 'base64', 
    iv: input_iv 
    }); 
console.log('lol', rijndael.decrypt(ciphertext, 'base64', 'binary')); 
    return rtrim(rijndael.decrypt(ciphertext, 'base64', 'binary'), '\0'); 
} 

exports.decrypt = decrypt; 
exports.encrypt = encrypt; 

encrypt.js - 它是例如用於加密。

var crypto = require('crypto'); 

var key = new Buffer('theonetruesecretkeytorulethemall', 'utf-8').toString('base64'); //secret key to decrypt 

var iv = crypto.randomBytes(32).toString('base64'); 

console.log({"key":key, "iv":iv}); 
var rijndael = require('./r256'); 
var plaintext = 'lalala'; //text to encrypt 

var ciphertext = rijndael.encrypt(plaintext, key, iv); 
console.log({"ciphertext":ciphertext}); 

這裏是解密 PHP的例子。

<?php 
echo "<PRE>"; 
$mcrypt_method = MCRYPT_RIJNDAEL_256; 
$mcrypt_mode = MCRYPT_MODE_ECB; 
$mcrypt_iv = '123456'; //needed only for encryption, but needed for mcrypt_generic_init, so for decryption doesn't matter what is IV, main reason it is IV can exist. 
$mcrypt_key = 'theonetruesecretkeytorulethemall'; 
$data_to_decrypt = base64_decode('ztOS/MQgJyKJNFk073oyO8KklzNJxfEphu78ok6iRBU='); //node.js returns base64 encoded cipher text 


$possible_methods = array_flip(mcrypt_list_algorithms()); 

if(empty($possible_methods[$mcrypt_method])) 
{ 
    echo "method $mcrypt_method is impossible".PHP_EOL; 
    exit(); 
} 

$possible_modes = array_flip(mcrypt_list_modes()); 
if(empty($possible_modes[$mcrypt_mode])) 
{ 
    echo "mode $mcrypt_mode is impossible".PHP_EOL; 
    exit(); 
} 

if([email protected]_get_block_size($mcrypt_method, $mcrypt_mode)) 
{ 
    echo "method $mcrypt_method does not support mode $mcrypt_mode".PHP_EOL; 
    exit(); 
} 

$mcrypt = mcrypt_module_open($mcrypt_method,'', $mcrypt_mode, ''); 

$ivsize = mcrypt_enc_get_iv_size($mcrypt); 

if($ivsize != strlen($mcrypt_iv)) 
{ 
    $mcrypt_iv = str_pad($mcrypt_iv, $ivsize, '#'); 
} 

if($ivsize < strlen($mcrypt_iv)) 
{ 
    $mcrypt_iv=substr($mcrypt_iv,0,$ivsize); 
} 

$keysize = mcrypt_enc_get_key_size($mcrypt); 
if($keysize != strlen($mcrypt_key)) 
{ 
    $mcrypt_key = str_pad($mcrypt_key, $keysize, '#'); 
} 

if($keysize < strlen($mcrypt_key)) 
{ 
    $mcrypt_key=substr($mcrypt_key,0,$keysize); 
} 


$mcrypt_isblock = (int)mcrypt_enc_is_block_mode($mcrypt); 
$mcrypt_blocksize = mcrypt_enc_get_block_size($mcrypt); 
$mcrypt_method = mcrypt_enc_get_algorithms_name($mcrypt); 
$mcrypt_mode = mcrypt_enc_get_modes_name($mcrypt); 

echo "used method=$mcrypt_method \nmode=$mcrypt_mode \niv=$mcrypt_iv \nkey=$mcrypt_key \nkey with blocksize=$mcrypt_blocksize \nisblock=$mcrypt_isblock".PHP_EOL; 

if(mcrypt_generic_init($mcrypt,$mcrypt_key,$mcrypt_iv)< 0) 
{ 
    echo "mcrypt_generic_init failed...".PHP_EOL; 
    exit(); 
} 


$result = mdecrypt_generic($mcrypt, $data_to_decrypt); 

echo PHP_EOL."decryption result|".$result.'|'; 

mcrypt_generic_deinit($mcrypt); 

P.S. 我不知道爲什麼,但Node.js忽略IV(在我的例子中),所以密碼將永遠是相同的。 PHP總是使用IV,它應該是嚴格的長度,所以PHP總是返回不同的密碼。但我反過來試了一下(由PHP加密並由Node.js解密),並且它工作正常。

相關問題