2

DES 暗号化を使用する C# 暗号化方式があります。私が作成しているnode.js APIでその値を復号化する必要があります。API で復号化メソッドのほとんどを再作成することができましたが、復号化するシークレットと値を渡すと、別の結果が得られます。

暗号化.cs

public static string Encrypt(string toEncrypt, string key)
{
    var des = new DESCryptoServiceProvider();
    var ms = new MemoryStream();

    des.Key = HashKey(key, des.KeySize / 8);
    des.IV = HashKey(key, des.KeySize / 8);
    string s = Encoding.UTF8.GetString (des.Key);
    des.IV = Encoding.UTF8.GetBytes (key);
    byte[] inputBytes = Encoding.UTF8.GetBytes(toEncrypt);

    var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
    cs.Write(inputBytes, 0, inputBytes.Length);
    cs.FlushFinalBlock();

    return HttpServerUtility.UrlTokenEncode(ms.ToArray());
}

public static string Decrypt(string toDecrypt, string key)
{
    var des = new DESCryptoServiceProvider();
    var ms = new MemoryStream();

    des.Key = HashKey(key, des.KeySize / 8);
    des.IV = HashKey(key, des.KeySize / 8);
    byte[] inputBytes = HttpServerUtility.UrlTokenDecode(toDecrypt);

    var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
    cs.Write(inputBytes, 0, inputBytes.Length);
    cs.FlushFinalBlock();

    var encoding = Encoding.UTF8;
    return encoding.GetString(ms.ToArray());
}

public static byte[] HashKey(string key, int length)
{
    var sha = new SHA1CryptoServiceProvider();
    byte[] keyBytes = Encoding.UTF8.GetBytes(key);
    byte[] hash = sha.ComputeHash(keyBytes);
    byte[] truncateHash = new byte[length];
    Array.Copy(hash, 0, truncateHash, 0, length);
    return truncateHash;
}

これは私が継承したコードです。これまでのところ、これを再作成することができました。

app.js

var keyHex = 'Secret'
var ciphertext = 'EncryptedValue'          
// Decrypt
var keyBytes = CryptoJS.enc.Utf8.parse(keyHex)
var sh1KeyVal = CryptoJS.SHA1(keyBytes)
var trunc = convertWordArrayToUint8Array(sh1KeyVal).slice(0, 8)
var decoded = decodeURI(ciphertext)
var key = trunc.toString(CryptoJS.enc.Utf8)
var bytes  = CryptoJS.DES.decrypt(decoded, key, { iv: key });
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log('Message: ', originalText);

シークレットのハッシュ プロセスは、私が再作成できたものtruncであり、APIの値がHashKeyメソッドが出力するバイト配列と同じであることを確認しました。

ただし、var bytes = CryptoJS.DES.decrypt(decoded, key, { iv: key });それを使用して単純な暗号化を行うと、C#メソッドとは異なる暗号化された値が得られるため、復号化が失敗すると思います。

私が見つけたものですが、対処方法がわからないのは、キーの値と復号化する値を渡すときに、それらが文字列である必要があるということですが、C# バージョンでは、CryptoStream はバイト配列を取るため、私がしなければならないことは何ですか?復号化する値を文字列として渡しますが、これが効果があるかどうかはわかりません。キーについても同じことがDESCryptoServiceProvider言えます。 はキーと iv をバイト配列として受け入れますが、crypto-js 切り捨てられた配列を変換すると、バイト配列のリテラル テキストが変換されるだけです。現在、次を使用してその変換を試みています。

var key = trunc.toString(CryptoJS.enc.Utf8)

プロセスのステップを見逃していますか? 何か見逃していませんか?

4

1 に答える 1

3

C# コードの暗号化と復号化の部分では、IV は で決定されdes.IV = HashKey(key, des.KeySize / 8)ます。暗号化部分では、この値は後で で上書きされdes.IV = Encoding.UTF8.GetBytes(key)ます。
その結果、暗号化と復号化では異なる IV が使用され、CBC モードのコンテキストでは、復号化後に平文の始まりが破損します。それとは別に、キーに対して例外がスローされるため、IV の長さが 8 バイトに等しくありません。
おそらく、IV の上書きはコピー/貼り付けエラーです。したがって、この行des.IV = Encoding.UTF8.GetBytes(key)は以下では無視されます。

この問題とは別に、2 つのコードは次の点で異なります。

  • C# コードでHttpServerUtility.UrlTokenEncode()は、Base64urlエンコーディングを実行し、通常のパディング バイト ( ) の代わりに Base64 パディング バイト数 0、1、または 2 を追加します=はURL エンコーディングをデコードするdecodeURI()ため、これを JavaScript コードで使用してもデコードできません。さらに、CryptoJS は Base64 エンコードを処理できるため、Base64 への変換は Base64url デコードよりも効率的です。Base64 への変換は、たとえば次のようにして可能です。decodeURI()

    function toBase64(b64url){
        var b64withoutPadding = b64url.substring(0, b64url.length - 1).replace(/-/g, '+').replace(/_/g, '/')
        var numberPaddingBytes = parseInt(b64url.substring(b64url.length - 1))
        var b64 = b64withoutPadding.padEnd(b64withoutPadding.length + numberPaddingBytes, '=');
        return b64 
    }
    
  • C# コードでは、SHA1 ハッシュの最初の 8 バイトが DES キーとして使用されます。WordArrayCryptoJS コードは、CryptoJS コードで正しく実装されていないとして DES キーを必要とします。可能な実装は次のとおりです。

    var keyDES = CryptoJS.lib.WordArray.create(sh1KeyVal.words.slice(0, 8 / 4));
    

これらの変更により、CryptoJS コードで復号化が可能になります。次の例では、暗号文は C# コードで生成されています。

var key = 'my passphrase'
var ciphertextB64url = 'jUtdTa7mUnBrL1yW5uA85GrD2mwUFLOzzsiZH0chPWo1'
var ciphertextB64 = toBase64(ciphertextB64url);

var keyUtf8 = CryptoJS.enc.Utf8.parse(key)
var sha1KeyVal = CryptoJS.SHA1(keyUtf8)
var keyDES = CryptoJS.lib.WordArray.create(sha1KeyVal.words.slice(0, 8 / 4));

var bytes  = CryptoJS.DES.decrypt(ciphertextB64, keyDES, { iv: keyDES });
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log('Message: ', originalText); // Message:  The quick brown fox jumps...

function toBase64(b64url){
    var b64withoutPadding = b64url.substring(0, b64url.length - 1).replace(/-/g, '+').replace(/_/g, '/')
    var numberPaddingBytes = parseInt(b64url.substring(b64url.length - 1))
    var b64 = b64withoutPadding.padEnd(b64withoutPadding.length + numberPaddingBytes, '=');
    return b64 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

どちらのコードにも深刻な脆弱性があることに注意してください (s. コメントも):

  • DES は安全でないアルゴリズムです。AESなどを使用することをお勧めします。
  • SHA1 も安全ではありません。SHA256 などを選択することをお勧めします。
  • ダイジェストからキーを導出することは安全ではありません。Argon2 や PBKDF2 などの信頼性の高いキー派生関数を使用することをお勧めします。
  • キーを IV として使用することは、一般的に安全ではありません。正しい方法は、暗号化ごとにランダムな IV を生成することです。非秘密 IV は、暗号文 (通常は連結) と一緒に渡されます。
于 2021-05-20T11:58:19.460 に答える