You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 17:09:01