AES GCM 加解密:PHP VS C# BouncyCastle

Posted

技术标签:

【中文标题】AES GCM 加解密:PHP VS C# BouncyCastle【英文标题】:AES GCM encryption and decryption: PHP VS C# BouncyCastle 【发布时间】:2019-11-14 23:25:15 【问题描述】:

我目前正在将我的 C# AES-GCM 加密代码转换为 php。但是,经过一番研究,我的PHP系统加密的文本不能被C#解密。我想知道这两个代码是否有任何区别:

C# 与 BouncyCastle:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;

//the helper for all AES methods
public class AESHelper 

    private const int KEY_BIT_SIZE = 256;
    private const int MAC_BIT_SIZE = 128;
    private const int NONCE_BIT_SIZE = 128;

    private readonly SecureRandom random;

    private static AESHelper instance;
    public static AESHelper Instance //property of this class. Create an instance if it is not created yet
    
        get
        
            if (instance == null)
                instance = new AESHelper();
            return instance;
        
    

    public AESHelper()
    
        random = new SecureRandom();
    

    //decrypt with strings
    public string Decrypt(string message, string key, int nonSecretPayloadLength = 0)
    
        if (string.IsNullOrEmpty(message))
            throw new ArgumentException("Message required!", "message");
        var decodedKey = Convert.FromBase64String(key);
        var cipherText = Convert.FromBase64String(message);
        var plainText = DecryptWithKey(cipherText, decodedKey, nonSecretPayloadLength);
        return Encoding.UTF8.GetString(plainText);
    

    //encrypt with strings
    public string Encrypt(string text, string key, byte[] nonSecretPayload = null)
    
        if (string.IsNullOrEmpty(text))
            throw new ArgumentException("Text required!", "text");
        var decodedKey = Convert.FromBase64String(key);
        var plainText = Encoding.UTF8.GetBytes(text);
        var cipherText = EncryptWithKey(plainText, decodedKey, nonSecretPayload);
        return Convert.ToBase64String(cipherText);
    

    //create new key
    public string NewKey()
    
        var key = new byte[KEY_BIT_SIZE / 8];
        random.NextBytes(key);
        return Convert.ToBase64String(key);
    

    //decrypt with byte array
    private byte[] DecryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength = 0)
    
        if (key == null || key.Length != KEY_BIT_SIZE / 8)
            throw new ArgumentException(String.Format("Key needs to be 0 bit!", KEY_BIT_SIZE), "key");
        if (message == null || message.Length == 0)
            throw new ArgumentException("Message required!", "message");

        using (var cipherStream = new MemoryStream(message))
            using (var cipherReader = new BinaryReader(cipherStream))
        
            var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
            var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
            cipher.Init(false, parameters);
            var cipherText = cipherReader.ReadBytes(message.Length - nonSecretPayloadLength - nonce.Length);
            var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
            try
            
                var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
                cipher.DoFinal(plainText, len);
            
            catch (InvalidCipherTextException)
            
                return null;
            
            return plainText;
        
    

    //encrypt with byte array
    private byte[] EncryptWithKey(byte[] text, byte[] key, byte[] nonSecretPayload = null)
    
        if (key == null || key.Length != KEY_BIT_SIZE / 8)
            throw new ArgumentException(String.Format("Key needs to be 0 bit!", KEY_BIT_SIZE), "key");

        nonSecretPayload = nonSecretPayload ?? new byte[]  ;
        var nonce = new byte[NONCE_BIT_SIZE / 8];
        random.NextBytes(nonce, 0, nonce.Length);
        var cipher = new GcmBlockCipher(new AesEngine());
        var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
        cipher.Init(true, parameters);
        var cipherText = new byte[cipher.GetOutputSize(text.Length)];
        var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
        cipher.DoFinal(cipherText, len);
        using (var combinedStream = new MemoryStream())
        
            using (var binaryWriter = new BinaryWriter(combinedStream))
            
                binaryWriter.Write(nonSecretPayload);
                binaryWriter.Write(nonce);
                binaryWriter.Write(cipherText);
            
            return combinedStream.ToArray();
        
    

这里是 PHP 系统:

<?php
    echo '<pre>';

    $hash_string = 'qIANSOwtdfF4y5Yk33ZLE5s6KwKBAeu6qzJRG84Sjjo=';
    echo "password : ";
    var_dump($hash_string);
    echo '<hr>';
    $decode_string = base64_decode($hash_string);
    $app_cc_aes_key = substr($decode_string, 0, 32);
    $cipher = 'aes-256-gcm';
    $iv_len = openssl_cipher_iv_length($cipher);
    echo "app_cc_aes_key : ";
    var_dump($app_cc_aes_key);
    echo '<br>';
    echo "cipher :";
    var_dump($cipher);
    echo '<hr>';

    $data = '7bc9d6ae-982f-11e9-bc42-526af7764f64';
    echo "data : $data";
    echo '<hr>';

    $tag_length = 16;
    $iv = openssl_random_pseudo_bytes($iv_len);
    $tag = "";
    $encrypt = openssl_encrypt($data, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
    $encrypt_text = base64_encode($iv.$tag.$encrypt);
    echo "encrypt :";
    var_dump($encrypt);
    echo '<br>';
    echo "encrypt_text :";
    var_dump($encrypt_text);
    echo '<hr>';

    $decoded_text = base64_decode($encrypt_text);
    $iv = substr($decoded_text, 0, $iv_len);
    $tag = substr($decoded_text, $iv_len, $tag_length);
    $ciphertext = substr($decoded_text, $iv_len + $tag_length);
    $decrypt_text = openssl_decrypt($ciphertext, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag);
    echo "decrypt_text : $decrypt_text";
    echo '<hr>';
?>

谁能告诉我 PHP 代码中是否有某些东西缺失或不同,导致它们以不同的方式完成?或者如果 PHP 函数和 BouncyCastle 函数之间存在一些内部差异导致它们不同?

【问题讨论】:

文本 ... 无法被 C# 解密 ... 这不是对问题的有用描述。究竟是什么错误?有例外吗?如果是这样,请在例外中包含完整信息。解密是否在进行,但结果不是您所期望的?如果是这样,请解释发生了什么以及您的预期。 【参考方案1】:

在 C# 代码中,数据在加密过程中按以下顺序连接:

nonSecretPyloadnoncecipherText

这里cipherText由两部分组成,加密消息和认证标签。将标签附加到加密消息是由GcmBlockCipher#DoFinal 自动完成的。

在 PHP 代码中,数据在加密过程中按以下顺序连接:

$iv$tag$encrypt

这里的$ivnonce 的对应物。与GcmBlockCipher#DoFinal 相比,PHP 方法openssl_encrypt 仅返回加密消息($encrypt)。身份验证标签在一个单独的变量中返回(第 6 个openssl_encrypt-参数$tag)。因此,$tag$encryptcipherText 以相反的顺序对应。 额外的认证数据,即nonSecretPyload的对应部分在PHP代码中根本不考虑。

很明显,两个代码中各个组件的顺序是不同的。这意味着在 C# 代码中加密的消息不能在 PHP 代码中解密(反之亦然)。为此,PHP 代码中的顺序必须更改如下:

$aad$iv$encrypt$tag

这里的$aadnonSecretPyload 的对应物。必须在加密部分和解密部分调整顺序(以及对附加验证数据的考虑)。

1234563 987654323@。为了兼容性,必须在两个代码中使用统一的 IV 长度!

【讨论】:

以上是关于AES GCM 加解密:PHP VS C# BouncyCastle的主要内容,如果未能解决你的问题,请参考以下文章

PHP如何实现AES加解密

如何使用 AES-GCM 对 C#.NET 加密()然后 JS WebCryptoApi 解密()?

使用 C# 与 PHP 的 AES GCM 加密

PHP的aes加解密算法

AES GCM 解密绕过 JAVA 中的身份验证

python Crypto AES-256-ECB 与PHP之间完成加解密