【问题标题】:C# <-> PHP dynamic AES key exchangeC# <-> PHP 动态 AES 密钥交换
【发布时间】:2015-02-19 17:20:16
【问题描述】:

我想用 C# 和 PHP 之间的动态密钥交换创建一个加密的通信系统。目前我有一个有效的加密/解密 PHP PHP 和 C# C# 但由于某种原因它不能正常工作 PHP C#。问题是 C# 代码解密的字符串生成的输出比 PHP 预期的要长,但是当将输出视为简单字符串时,数据是相同的。例如,从 C# 发送到 PHP 的字符串“daa”,解密长度为 28,这不是应该的。从 PHP 发送到 C# 的字符串也是如此,我得到一个编译器错误 ArgumentException: length

C#代码:

public static string EncryptTest(string input)
{
    string key = "256 bit key (32 char)";

    input = Md5Sum(input).Substring(0, 4) + input;

    var encoding = new UTF8Encoding();
    var Key = encoding.GetBytes(key);
    byte[] encrypted;
    byte[] result;

    using (var rj = new RijndaelManaged())
    {
        try
        {
            rj.Padding = PaddingMode.PKCS7;
            rj.Mode = CipherMode.CBC;
            rj.KeySize = 256;
            rj.BlockSize = 256;
            rj.Key = Key;
            rj.GenerateIV();

            using (ICryptoTransform encryptor = rj.CreateEncryptor())
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter writer = new StreamWriter(cs))
                        {
                            writer.Write(input);
                        }
                    }

                    encrypted = ms.ToArray();
                    result = new byte[rj.BlockSize / 8 + encrypted.Length];

                    // Result is built as: IV (plain text) + Encrypted(data)
                    Array.Copy(rj.IV, result, rj.BlockSize / 8);
                    Array.Copy(encrypted, 0, result, rj.BlockSize / 8, encrypted.Length);
                }
            }
        }
        finally
        {
            rj.Clear();
        }
    }
    return Convert.ToBase64String(result);
}

public static string DecryptTest(string input)
{
    string key = "256 bit key (32 char)";

    byte[] data = Convert.FromBase64String(input);

    if (data.Length < 32)
        return null;

    var encoding = new UTF8Encoding();
    var Key = encoding.GetBytes(key);

    using (RijndaelManaged aes = new RijndaelManaged())
    {
        aes.Padding = PaddingMode.PKCS7;
        aes.Mode = CipherMode.CBC;
        aes.KeySize = 256;
        aes.BlockSize = 256;
        aes.Key = Key;

        // Extract the IV from the data first.
        byte[] iv = new byte[aes.BlockSize / 8];
        Array.Copy(data, iv, iv.Length);
        aes.IV = iv;

        // The remainder of the data is the encrypted data we care about.
        byte[] encryptedData = new byte[data.Length - iv.Length];
        Array.Copy(data, iv.Length, encryptedData, 0, encryptedData.Length);

        using (ICryptoTransform decryptor = aes.CreateDecryptor())
        {
            using (MemoryStream ms = new MemoryStream(encryptedData))
            {
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader reader = new StreamReader(cs))
                    {
                        string output = reader.ReadToEnd();

                        if (output.Length < 4)
                            return null;

                        string dataHash = output.Substring(0, 4);
                        string dataInput = output.Substring(4);
                        string dataInputHash = Md5Sum(dataInput).Substring(0, 4);

                        if (dataHash != dataInputHash)
                            return null;

                        return dataInput;
                    }
                }
            }
        }
    }
}

private static string Md5Sum(string strToEncrypt)
{
    UTF8Encoding ue = new UTF8Encoding();
    byte[] bytes = ue.GetBytes(strToEncrypt);

    MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
    byte[] hashBytes = md5.ComputeHash(bytes);

    string hashString = "";

    for (int i = 0; i < hashBytes.Length; i++)
    {
        hashString += Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
    }

    return hashString.PadLeft(32, '0');
}

PHP 代码:

$key = "256 bit key (32 char)";

function iv()
{
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    return mcrypt_create_iv($iv_size, MCRYPT_RAND);
}
function encrypt($data, $key32)
{
    # Prepend 4-chars data hash to the data itself for validation after decryption
    $data = substr(md5($data), 0, 4).$data;
    # Prepend $iv to decrypted data
    $iv = iv();
    $enc = $iv.mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key32, $data, MCRYPT_MODE_CBC, $iv);
    return base64_encode($enc);
}
function decrypt($data, $key32)
{
    $data = base64_decode($data);
    if ($data === false || strlen($data) < 32)
        return null;
    $iv = substr($data, 0, 32);
    $encrypted = substr($data, 32);
    $decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key32, $encrypted, MCRYPT_MODE_CBC, $iv), "\0");
    if ($decrypted === false || is_null($decrypted) || strlen($decrypted) < 4)
        return null;
    $dataHash = substr($decrypted, 0, 4);
    $data = substr($decrypted, 4);
    if (substr(md5($data), 0, 4) !== $dataHash)
        return null; // it breaks here, md5 sum is not correct because of the length
    return $data;
} 

【问题讨论】:

  • DecryptTest 正在使用不同的密钥,但我假设这是您忘记编辑的内容。
  • 是的,我的错。所有功能都使用相同的 256 位密钥。
  • 您实际上并没有使用 AES,您使用的是块大小为 256 位的 Rijndael。但在双方,所以这不是解决方案。
  • @MaartenBodewes-owlstead 但是数据被正确加密/解密,它失败的只是 C# 中的字节长度,我不知道如何解决。
  • 请注意,要使用传输协议,您还应该保护您的 IV 和密文尤其是 CBC 密文,以确保完整性和真实性(例如,使用 HMAC 标签)。否则,攻击者可以更改明文,或者更糟糕的是,使用填充 Oracle 攻击完全破坏机密性。

标签: c# php encryption dynamic exchange-server


【解决方案1】:

PHP / mcrypt 没有使用 PKCS#7 填充,它使用 0..n 字节的零填充,其中 n 是块大小。


要在加密前使用 PKCS#7-pad 明文,请使用:

$pad = $blockSize - (strlen($data) % $blockSize);
$pdata = $data . str_repeat(chr($pad), $pad);

解密后取消填充明文只需执行:

$pad = ord($pdata[strlen($pdata) - 1]);
$data = substr($pdata, 0, strlen($pdata) - $pad);

PKCS#7 现在是填充的临时标准。零填充是不确定的;如果明文本身以零结尾,它可能会改变明文。

【讨论】:

  • 所有组合现在都可以使用,除了 PHP -> C# 解密。当我尝试在 C# 中解密用 PHP 加密的字符串时,出现以下异常:CryptographicException: Bad PKCS7 padding。长度无效 126。Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException知道为什么吗?
  • 应该是以字节为单位的块大小,抱歉,同时尝试用 PHPDoc 创建一个函数...
  • 我在 PHP 中使用它来加密块大小 $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
  • 你是在 PHP 中做 before 加密的填充,对吧?
  • 我尝试使用 try..finally 块,并在 finally 中使用 aes.Clear(),但结果相同。我已经编辑了我的问题以将我当前的 C# 代码用于解密,也许我遗漏了什么?
猜你喜欢
  • 1970-01-01
  • 2014-10-03
  • 1970-01-01
  • 2016-09-24
  • 2019-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多