将 Elixir 加密和解密逻辑映射到 PHP,反之亦然
Map Elixir encryption and decryption logic to PHP and vice versa
我正在尝试使用 Elixir 和 PHP 编写逻辑来加密和解密文本。我的目标是解密在 Elixir 中加密的数据,反之亦然。两种算法都工作正常,但我遇到的唯一问题是将初始化向量与密文分开,并将二进制转换为 PHP 中的字符串,以及在 Elixir 中解密文本。
长生不老药实施:
defmodule Crypto.AES do
@block_size 16
@secret_key "something secret here"
def encrypt(text) do
secret_key_hash = make_hash(@secret_key, 32)
IO.inspect secret_key_hash
# create random Initialisation Vector
iv = :crypto.strong_rand_bytes(@block_size)
text = pad_pkcs7(text, @block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, text, true )
encrypted_text = ( iv <> <<"::">> <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(@secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, rp::binary-2, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
@doc """
Pad the `message` by extending it to the nearest `blocksize` boundary,
appending the number of bytes of padding to the end of the block.
If the original `message` is a multiple of `blocksize`, an additional block
of bytes with value `blocksize` is added.
"""
def pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
@doc """
Remove the PKCS#7 padding from the end of `data`.
"""
def unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
def make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP 实施:
<?php
$ENCRYPTION_KEY = 'something secret here';
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialisation Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return base64_encode($encrypted_text . '::' . $iv);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$split = explode('::', base64_decode($ciphertext), 2);
list($iv, $encrypted_text) = array_pad($split, 2, null);
$plain_text = openssl_decrypt(
$encrypted_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return $plain_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
$ct = encrypt("hello");
// echo $ct."\n";
echo decrypt("Sr4nMnMdDHhUQcnW6RwZ2Do6rhBh/ytW1W/x7Xx2/Xrv3A==")."\n";
?>
请提出一些可能的解决方案。谢谢
两个代码不兼容有两个原因:
PHP密码returns加密时默认Base64编码的密文。这必须禁用,因为 Base64 编码是在 IV 和密文连接后 显式 执行的。通过将 OPENSSL_RAW_DATA
作为 openssl_encrypt()
中的第 4 个参数传递,可以禁用。同样适用于解密:
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA, // Fix: Don't Base64 encode the ciphertext
$iv
);
...
$plain_text = openssl_decrypt(
$encrted_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA, // Fix: Don't Base64 decode the ciphertext
$iv
);
正如评论中已经指出的那样,这两种代码在解密过程中分离时都采用不同的 IV 和密文顺序。同时,您已将 PHP 代码中的解密期间的分隔调整为 Elixir 代码的分隔。
而且对于加密,两种代码都使用不同顺序的 IV 和密文。关于加密的PHP代码修改仍然缺失:
...
return base64_encode($iv . '::' . $encrypted_text); // Reverse order
通过这些更改,这两个代码在功能上是相同的,用 PHP 代码生成的密文可以用 Elixir 代码解密,反之亦然。
关于连接的注意事项:IV和密文都可以以一定的概率包含分隔符::
,这会导致问题。最好在不使用 分隔符的情况下 连接数据,并使用已知长度的 IV 进行分隔。
此外,使用散列函数进行密钥推导是不安全的,最好使用可靠的密钥推导函数,如 PBKDF2。此外,采用十六进制编码密钥而不是二进制数据会降低安全性。
根据@Topaco 的建议(PBKDF2 除外,稍后添加),这是完整的解决方案。
灵药 - git-link
defmodule Crypto.AES do
@block_size 16
@secret_key "put something secret here"
def encrypt(plain_text) do
secret_key_hash = make_hash(@secret_key, 32)
# create Initialisation Vector
iv = :crypto.strong_rand_bytes(@block_size)
padded_text = pad_pkcs7(plain_text, @block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, padded_text, true )
# concatenate IV for decryption
encrypted_text = ( iv <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(@secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
defp pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
defp unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
defp make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP - gist-link
<?php
$ENCRYPTION_KEY = "put something secret here";
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialization Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
# concatenate the IV for decryption
return base64_encode($iv . $encrypted_text);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$ciphertext = base64_decode($ciphertext);
// get Initialization Vector part (16 bytes long)
$iv = substr($ciphertext, 0, 16);
// rest is actual cipher text
$ciphertext = substr($ciphertext, 16);
$decrypted_text = openssl_decrypt(
$ciphertext,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
return $decrypted_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
// $ct = encrypt("code");
// $dt = decrypt($ct);
// echo $ct."\n";
// echo $dt."\n";
?>
我正在尝试使用 Elixir 和 PHP 编写逻辑来加密和解密文本。我的目标是解密在 Elixir 中加密的数据,反之亦然。两种算法都工作正常,但我遇到的唯一问题是将初始化向量与密文分开,并将二进制转换为 PHP 中的字符串,以及在 Elixir 中解密文本。
长生不老药实施:
defmodule Crypto.AES do
@block_size 16
@secret_key "something secret here"
def encrypt(text) do
secret_key_hash = make_hash(@secret_key, 32)
IO.inspect secret_key_hash
# create random Initialisation Vector
iv = :crypto.strong_rand_bytes(@block_size)
text = pad_pkcs7(text, @block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, text, true )
encrypted_text = ( iv <> <<"::">> <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(@secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, rp::binary-2, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
@doc """
Pad the `message` by extending it to the nearest `blocksize` boundary,
appending the number of bytes of padding to the end of the block.
If the original `message` is a multiple of `blocksize`, an additional block
of bytes with value `blocksize` is added.
"""
def pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
@doc """
Remove the PKCS#7 padding from the end of `data`.
"""
def unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
def make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP 实施:
<?php
$ENCRYPTION_KEY = 'something secret here';
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialisation Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return base64_encode($encrypted_text . '::' . $iv);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$split = explode('::', base64_decode($ciphertext), 2);
list($iv, $encrypted_text) = array_pad($split, 2, null);
$plain_text = openssl_decrypt(
$encrypted_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return $plain_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
$ct = encrypt("hello");
// echo $ct."\n";
echo decrypt("Sr4nMnMdDHhUQcnW6RwZ2Do6rhBh/ytW1W/x7Xx2/Xrv3A==")."\n";
?>
请提出一些可能的解决方案。谢谢
两个代码不兼容有两个原因:
PHP密码returns加密时默认Base64编码的密文。这必须禁用,因为 Base64 编码是在 IV 和密文连接后 显式 执行的。通过将
OPENSSL_RAW_DATA
作为openssl_encrypt()
中的第 4 个参数传递,可以禁用。同样适用于解密:$encrypted_text = openssl_encrypt( $plain_text, $ENCRYPTION_ALGORITHM, $EncryptionKey, OPENSSL_RAW_DATA, // Fix: Don't Base64 encode the ciphertext $iv ); ... $plain_text = openssl_decrypt( $encrted_text, $ENCRYPTION_ALGORITHM, $EncryptionKey, OPENSSL_RAW_DATA, // Fix: Don't Base64 decode the ciphertext $iv );
正如评论中已经指出的那样,这两种代码在解密过程中分离时都采用不同的 IV 和密文顺序。同时,您已将 PHP 代码中的解密期间的分隔调整为 Elixir 代码的分隔。
而且对于加密,两种代码都使用不同顺序的 IV 和密文。关于加密的PHP代码修改仍然缺失:... return base64_encode($iv . '::' . $encrypted_text); // Reverse order
通过这些更改,这两个代码在功能上是相同的,用 PHP 代码生成的密文可以用 Elixir 代码解密,反之亦然。
关于连接的注意事项:IV和密文都可以以一定的概率包含分隔符::
,这会导致问题。最好在不使用 分隔符的情况下 连接数据,并使用已知长度的 IV 进行分隔。
此外,使用散列函数进行密钥推导是不安全的,最好使用可靠的密钥推导函数,如 PBKDF2。此外,采用十六进制编码密钥而不是二进制数据会降低安全性。
根据@Topaco 的建议(PBKDF2 除外,稍后添加),这是完整的解决方案。
灵药 - git-link
defmodule Crypto.AES do
@block_size 16
@secret_key "put something secret here"
def encrypt(plain_text) do
secret_key_hash = make_hash(@secret_key, 32)
# create Initialisation Vector
iv = :crypto.strong_rand_bytes(@block_size)
padded_text = pad_pkcs7(plain_text, @block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, padded_text, true )
# concatenate IV for decryption
encrypted_text = ( iv <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(@secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
defp pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
defp unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
defp make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP - gist-link
<?php
$ENCRYPTION_KEY = "put something secret here";
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialization Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
# concatenate the IV for decryption
return base64_encode($iv . $encrypted_text);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$ciphertext = base64_decode($ciphertext);
// get Initialization Vector part (16 bytes long)
$iv = substr($ciphertext, 0, 16);
// rest is actual cipher text
$ciphertext = substr($ciphertext, 16);
$decrypted_text = openssl_decrypt(
$ciphertext,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
return $decrypted_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
// $ct = encrypt("code");
// $dt = decrypt($ct);
// echo $ct."\n";
// echo $dt."\n";
?>