1

私は、C# で Rijndael を使用して文字列 (json) を暗号化しようとし、PHP Web サービスに提供できる文字列を考え出しました。この Web サービスは、IV とマスターキー (それらが認識している) を使用して文字列をデコードします。PHP サービスと対話できる C# コードを作成する必要があります。PHP サービスを制御または所有していません。

暗号化のための PHP コードは次のとおりです。

function encrypt($plaintext) {
    $masterkey = 'masterKeyOfLength29Characters';
    $td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
    $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
    mcrypt_generic_init($td, $masterkey, $iv);
    $crypttext = mcrypt_generic($td, $plaintext);
    mcrypt_generic_deinit($td);
    return base64_encode($iv.$crypttext);
}
$param = array("key" => "value");
$encryptedString = rawurlencode(encrypt(json_encode($param)))

上記のコードは C# に変換する必要があるため、JSON を暗号化して PHP Web サービスに提供できます。

2 つの問題があります。1 つ目はマスターキーの長さ、2 つ目 (関連している可能性があります) は暗号化されたデータの rawurlencode です (この時点でテストするのは難しいです)。

var masterkey = "masterKeyOfLength29Characters";
var data = EncryptData(json, masterkey);
// Some code to URL Encode the data, I haven't gotten as far to test this
// since I can't encrypt with the key used in PHP, so I can't call the service
// to test the encoded string from my C# code.
data = HttpUtility.UrlEncode(data);
data = data.Replace("+", "%20");

public static string EncryptData(string json, string encryptionKey) {
    Rijndael rj = Rijndael.Create();
    rj.Mode = CipherMode.CBC;
    rj.Padding = PaddingMode.PKCS7;
    rj.BlockSize = 256;
    rj.KeySize = 256;
    rj.Key = Encoding.UTF8.GetBytes(encryptionKey); // ERROR here
    rj.GenerateIV();
    var encryptedJSON = EncryptStringToBytes(json, rj.Key, rj.IV);
    var r1 = Convert.ToBase64String(rj.IV);
    var r2 = Convert.ToBase64String(encryptedJSON);
    return r1 + r2;
}

EncryptStringToBytes はいくつかのチェックを行い、次のコードを使用します (インターネット上の多くの例から抜粋)。

using (Rijndael rijAlg = Rijndael.Create()) {
    // Basically I do the same here as above, and I could also generate
    // the IV here, but then I'd had to return it too. I know I can clean this
    // code up quite a bit, but I'd rather focus on getting it to work first ;)
    rijAlg.Mode = CipherMode.CBC;
    rijAlg.Padding = PaddingMode.PKCS7;
    rijAlg.BlockSize = 256;
    rijAlg.KeySize = 256;
    rijAlg.Key = Key;
    rijAlg.IV = IV;
    ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
    using (MemoryStream msEncrypt = new MemoryStream()) {
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
                swEncrypt.Write(plainText);
            }
            encrypted = msEncrypt.ToArray();
        }
    }
}

私が得るエラー:

Specified key is not a valid size for this algorithm.

したがって、要するに問題は次のとおりです。

1) なぜ PHP コードは Rijndael 256 (CBC モード) で長さ 29 のキーを受け入れ、私の C# は受け入れないのですか? 私はモードで遊んで、後でパディングを追加し、KeySize を設定しました (既に 256 のデフォルトでした)。ここで何が間違っているのかわかりません。

2) 長さ 32 のキーを使用すると、このキーが受け入れられ、コードが機能します。C# で復号化することもできます (ただし、PHP でこれをテストすることはできません)。問題 1 を解決してから、問題 2 に進みたいと思いますが、誰かがここで理解を深めてくれるかもしれません。暗号化された文字列には、IV に 1 つの '=' が含まれ、暗号化された json に 2x '==' (末尾) が含まれます。パディングなどについて読んだことがありますが、受け取ったPHPの例に「=」記号が表示されないのはなぜだろうと思っていました。繰り返しますが、おそらく問題 1 を修正した後は、これは問題にならないでしょう。

読んでくれてどうもありがとう。昨日試した一日の後、私は多くの異なるアプローチを試してみましたが、うまくいかないようです。

4

2 に答える 2

1

@artjom-b が言ったことに少し追加したいと思いました。

まず、それは機能します:-)

しかし、さらに変更する必要があります

rj.Padding = PaddingMode.PKCS7

使用する

rj.Padding = PaddingMode.Zeros 

また、技術的には、2 つの関数は同じものを返していません。PHP はバイナリ データの 2 つの連結ビットの base 64 を返しますが、C# は個別の b64 文字列の連結を返します。結果は、返される文字列の後半で異なります。

編集:ラフで準備ができている復号化ルーチン:

public string DecryptRijndael(byte[] cipherText, string password, byte[] iv)
{
    var key = new byte[32];
    Encoding.UTF8.GetBytes(password).CopyTo(key, 0);

    var cipher = new RijndaelManaged();
    cipher.Mode = CipherMode.CBC;
    cipher.Padding = PaddingMode.None;
    cipher.KeySize = 256;
    cipher.BlockSize = 256;
    cipher.Key = key;
    cipher.IV = iv;

    byte[] plain;
    using (var decryptor = cipher.CreateDecryptor())
    {
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
            {
                cs.Write(cipherText, 0, cipherText.Length);
                cs.FlushFinalBlock();
                plain = ms.ToArray();
            }
        }
    }
    return Encoding.UTF8.GetString(plain);
}

注意: Artjom B からのすべての注意事項と警告は引き続き適用されます。

于 2015-12-16T11:42:46.860 に答える
1

無効な長さのキーを問題なく受け入れる古いバージョンの PHP を使用しています。Rijndael は、16、24、および 32 バイトの鍵サイズをサポートしており、その中間はありません。PHP の mcrypt 拡張機能は、次の有効なキー サイズである 32 バイトまで、キーに 0x00 バイトを暗黙的に埋め込みます。C# でも同じことを行う必要があります。

byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;

ある程度のセキュリティを提供するには、キーのエントロピーが高くなければならないことに注意してください。パスワードはキーではないため、文字セットと使用可能な単語が限られているため、あまりエントロピーを提供しません。Argon2、scrypt、bcrypt、PBKDF2 などの使用可能な導出関数を使用して、高いコスト係数/反復回数とランダムなソルトを使用して、常にパスワードからキーを導出します。

また、暗号文に認証を追加する必要があります。そうしないと、攻撃者が知らないうちに暗号文を変更する可能性があります。これは、GCM/EAX などの認証モードを使用するか、暗号文に対して HMAC を実行して認証タグを生成することによって行われます。

于 2015-12-16T09:44:27.260 に答える