Elixir与PHP AES-256-CBC加解密双向互通问题求解
问题根源
- 两端IV与密文的拼接顺序相反,导致拆分内容完全错位
- PHP端
openssl_encrypt默认返回base64编码结果而非原始二进制,和Elixir的二进制加密输出不匹配 - 加解密padding逻辑不对齐:PHP默认自动添加PKCS7 padding,Elixir手动实现padding,逻辑冲突导致解密失败
- 用
explode拆分密文不可靠,若密文中碰巧出现::字符串会直接拆分失败
修正方案
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) # 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'; $IV_LENGTH = openssl_cipher_iv_length($ENCRYPTION_ALGORITHM); function encrypt($plain_text) { global $ENCRYPTION_KEY, $ENCRYPTION_ALGORITHM, $IV_LENGTH; $EncryptionKey = make_hash($ENCRYPTION_KEY, 32); // 生成随机IV $iv = openssl_random_pseudo_bytes($IV_LENGTH); // 手动添加PKCS7 padding $plain_text = pkcs7_pad($plain_text, $IV_LENGTH); // 加密参数指定返回原始二进制、关闭自动padding $encrypted_text = openssl_encrypt( $plain_text, $ENCRYPTION_ALGORITHM, $EncryptionKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv ); // 拼接顺序和Elixir对齐:IV + 分隔符 + 密文 return base64_encode($iv . '::' . $encrypted_text); } function decrypt($ciphertext) { global $ENCRYPTION_KEY, $ENCRYPTION_ALGORITHM, $IV_LENGTH; $EncryptionKey = make_hash($ENCRYPTION_KEY, 32); $decoded = base64_decode($ciphertext); // 硬拆分避免分隔符冲突:前16位是IV,跳过2位分隔符,剩余为密文 $iv = substr($decoded, 0, $IV_LENGTH); $encrypted_text = substr($decoded, $IV_LENGTH + 2); $decrypted_text = openssl_decrypt( $encrypted_text, $ENCRYPTION_ALGORITHM, $EncryptionKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv ); // 移除PKCS7 padding return pkcs7_unpad($decrypted_text); } function make_hash($text, $length) { $hash_key = hash("sha512", $text, false); return substr($hash_key, 0, $length); } // PKCS7填充实现,和Elixir逻辑对齐 function pkcs7_pad($data, $block_size) { $pad = $block_size - (strlen($data) % $block_size); return $data . str_repeat(chr($pad), $pad); } // PKCS7填充移除实现 function pkcs7_unpad($data) { $pad = ord(substr($data, -1)); return substr($data, 0, strlen($data) - $pad); } // 测试代码 $ct = encrypt("hello"); echo $ct."\n"; echo decrypt($ct)."\n"; ?>
验证说明
修改后两端生成的密文可互相解密,硬拆分逻辑彻底避免了IV拆分错误问题,padding和加密输出格式完全对齐,不存在二进制转换异常。
内容的提问来源于stack exchange,提问作者Mudassar
相关产品推荐
相关产品推荐

