57155
お世話になります。  
C#とPHPの間で暗号化した文字列をやりとりする必要があるのですが、PHP側でどのようにすればよいかよくわからずにいます。  
とりあえず、C#とPHPのコードを記載しますので、何かアドバイスをいただけますと幸いです。

## C#のコード

```

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using System.Windows.Forms;
    
    public class App{
    public static void Main(){
    var test = new test_crypt.test();
    string result = test.Encrypt("abc", "test");
    MessageBox.Show(result);
    string dresult = test.Decrypt(result, "test");
    MessageBox.Show(dresult);
    }
    }
    
    namespace test_crypt{
    public class test{
    private Encoding _encode = Encoding.UTF8;
    private byte[] _salt = Encoding.UTF8.GetBytes("1234567890");
    
    public string Encrypt(string text, string password){
    byte[] bytes = _encode.GetBytes(text);
    byte[] inArray = Encrypt(bytes, password);
    return Convert.ToBase64String(inArray);
    }
    
    private byte[] Encrypt(byte[] strBytes, string password){
    SymmetricAlgorithm algorithm = GetAlgorithm();
    byte[] key, iv;
    GenerateKeyFromPassword(password, algorithm.KeySize, out key, algorithm.BlockSize, out iv);
    algorithm.Key = key;
    algorithm.IV = iv;
    ICryptoTransform cryptoTransform = algorithm.CreateEncryptor();
    byte[] result = cryptoTransform.TransformFinalBlock(strBytes, 0, strBytes.Length);
    cryptoTransform.Dispose();
    return result;
    }
    
    public string Decrypt(string text, string password){
    byte[] strBytes = Convert.FromBase64String(text);
    byte[] bytes = Decrypt(strBytes, password);
    return _encode.GetString(bytes);
    }
    
    private byte[] Decrypt(byte[] strBytes, string password){
    SymmetricAlgorithm algorithm = GetAlgorithm();
    byte[] key, iv;
    GenerateKeyFromPassword(password, algorithm.KeySize, out key, algorithm.BlockSize, out iv);
    algorithm.Key = key;
    algorithm.IV = iv;
    ICryptoTransform cryptoTransform = null;
    try{
    cryptoTransform = algorithm.CreateDecryptor();
    return cryptoTransform.TransformFinalBlock(strBytes, 0, strBytes.Length);
    }
    catch (CryptographicException ex){
    throw new Exception("復号化に失敗しました。\n" + ex.Message);
    }
    finally{
    cryptoTransform.Dispose();
    }
    }
    
    protected void GenerateKeyFromPassword(string password, int keySize, out byte[] key, int blockSize, out byte[] iv){
    Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, _salt);
    rfc2898DeriveBytes.IterationCount = 1;
    key = rfc2898DeriveBytes.GetBytes(keySize / 8);
    iv = rfc2898DeriveBytes.GetBytes(blockSize / 8);
    }
    
    protected virtual SymmetricAlgorithm GetAlgorithm(){
    return new AesManaged();
    }
    }
    }
    
```

## PHPのコード

```

    <?php
    $method = 'AES-128-CBC';
    $password = 'test';
    $plain_text = 'abc';
    $salt = '1234567890';
    
    $key_iv = openssl_pbkdf2($password, $salt, 32+16, 1);
    $key = substr($key_iv, 0, 32);
    $iv = substr($key_iv, 32, 16);
    $encrypted_text = openssl_encrypt($plain_text, $method, $key, 0, $iv);
    $decrypted_text = openssl_decrypt($encrypted_text, $method, $key, 0, $iv);
    echo $encrypted_text."<br>";
    echo $decrypted_text."<br>";
    ?>
    
```

* * *

以上、よろしくお願いいたします。
CC BY-SA 4.0
2019-08-04T02:56:06.217
57148
2019-08-04T06:25:45.617
2019-08-04T06:25:45.617
29034
29034
question
php c#
C#とPHP間で暗号化文字列をやりとりする方法
C#のコードは[RFC2898](https://www.rfc-editor.org/rfc/rfc2898)に記載されているPBKDF2(PKCS #5
v2.0)に基づきkeyとivを作成し、AES-256-CBCで暗号化/復号化しているようです。PHP側でもPBKDF2に基づきkeyとivを作成すれば、同様に暗号化/復号化が可能になるはずです。PBKDF2用の関数[hash_pbkdf2](https://www.php.net/manual/ja/function.hash-
pbkdf2.php)が用意されていますので、こちらの関数を使用することで、同様のkeyとivを作成することができます。使用方法等はリンク先のPHPマニュアル等を確認してください。

異なるコード間でやり取りする場合の注意事項としては、同じPBKDF2に基づいていても、暗号アルゴリズム、ハッシュアルゴリズム、反復回数が同じでなければなりません。C#側の[AesManaged](https://docs.microsoft.com/ja-
jp/dotnet/api/system.security.cryptography.aesmanaged?view=netframework-4.8)はASEですが、鍵長を128,192,256で選択でき、指定しなかった場合にどれになるかはわかりませんでした。サイズを固定するか、あらかじめ確認しておいてください。C#側の[Rfc2898DeriveBytes](https://docs.microsoft.com/ja-
jp/dotnet/api/system.security.cryptography.rfc2898derivebytes?view=netframework-4.8)が使用するハッシュアルゴリズムはHMAC
SHA1固定のようですが、PHP側の`hash_pbkdf2`は複数選択できることに注意してください。なお、現在のコードでは、`rfc2898DeriveBytes.IterationCount
= 1;`から反復回数はたった1回です。

現在のコードにはセキュリティ上よろしくない点が二つあります(何故ダメなのかの理由は長くなるので、省略します)。これらを解決しておくことを推奨します。

  1. saltが固定である。  
saltを固定化してはいけません。saltは暗号論的に安全な乱数生成機(ハードウェアエントロピーによる乱数や暗号論的擬似乱数生成機等)を使用して生成してください。暗号論的に安全な乱数生成機とは、C#であれば[RNGCryptoServiceProvider](https://docs.microsoft.com/ja-
jp/dotnet/api/system.security.cryptography.rngcryptoserviceprovider?view=netframework-4.8)、PHPであれば[random_byets](https://www.php.net/manual/ja/function.random-
bytes.php)等です。  
暗号データを保存する際は、saltと暗号化されたデータをセットで保存しておくようにします。秘密にしておく必要があるのは、パスワードのみです。

  2. 反復回数が1回である。  
PBKDF2が書かれたRFC2898では反復回数を指定していませんが、1000回以上を推奨しています。1000回以上に増やしてください。なお、C#のRfc2898DeriveBytesのデフォルトは1000回です。
CC BY-SA 4.0
2019-08-04T05:22:31.717
57152
2019-08-04T05:22:31.717
2021-10-07T07:34:52.683
-1
7347
57148
answer
PHPでは[`openssl_pbkdf2`](https://www.php.net/manual/en/function.openssl-
pbkdf2.php)か[`hash_pbkdf2`](https://www.php.net/manual/ja/function.hash-
pbkdf2.php)で.NETの[`Rfc2898DeriveBytes`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.security.cryptography.rfc2898derivebytes?view=netframework-4.8)と同等の結果が得られます。

```

    <?php
    $password = 'test';
    $plain_text = 'abc';
    $salt = "1234567890";
    
    $keyiv = openssl_pbkdf2($password, $salt, 32+16, 1);
    //$keyiv = hash_pbkdf2("sha1", $password, $salt, 1, 32+16, TRUE);
    $key = substr($keyiv, 0, 32);
    $iv = substr($keyiv, 32, 16);
    
    $encrypted_text = openssl_encrypt($plain_text, 'AES-256-CBC', $key, 0, $iv);
    $decrypted_text = openssl_decrypt($encrypted_text, 'AES-256-CBC', $key, 0, $iv);
    echo $encrypted_text;
    echo $decrypted_text;
    ?>
    
```
CC BY-SA 4.0
2019-08-04T05:58:15.313
57155
2019-08-04T05:58:15.313
4236
57148
answer