erlang 将Elixir加密和解密逻辑Map到PHP,反之亦然

34gzjxbg  于 2022-12-08  发布在  Erlang
关注(0)|答案(2)|浏览(168)

我正在尝试用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";
?>

请提出一些可能的解决方案。谢谢

z4iuyo4d

z4iuyo4d1#

The two codes are incompatible for two reasons:

  • The PHP code returns the ciphertext Base64 encoded by default during encryption. This must be disabled because the Base64 encoding is performed explicitly after the concatenation of IV and ciphertext. Disabling is possible by passing OPENSSL_RAW_DATA as 4th parameter in openssl_encrypt() . The same applies for decryption:
$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
);
  • As already noted in the comment, both codes assume a different order of IV and ciphertext when separating during decryption. Menwhile you have adapted the separation during decryption in the PHP code to that of the Elixir code.

But also for encryption, both codes use a different order of IV and ciphertext. The modification of the PHP code concerning the encryption is still missing:

...
return  base64_encode($iv . '::' . $encrypted_text); // Reverse order

With these changes, the two codes are functionally identical and a ciphertext generated with the PHP code can be decrypted with the Elixir code and vice versa.
A note regarding concatenation: Both the IV and ciphertext can contain the separator :: with a certain probability, which can cause problems. It would be better to concatenate the data without a separator and use the known length of the IV for the separation.
Also, using a hash function for key derivation is insecure, better apply a reliable key derivation function like PBKDF2. Furthermore, taking the hex encoded key instead of the binary data reduces security.

ni65a41a

ni65a41a2#

根据@Topaco的建议(PBKDF2除外,稍后将添加),这里有完整的解决方案。
长生不老药

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
    $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";
?>

相关问题