24

.NET には、SecureString クラスがあります。これは、(たとえば) 文字列をハッシュするためにプレーンテキストが必要なため、試して使用するまではすべて問題ありません。ここでは、バイト配列を取り、バイト配列を出力するハッシュ関数が与えられた場合に、SecureString をハッシュする関数を作成してみました。

private static byte[] HashSecureString(SecureString ss, Func<byte[], byte[]> hash)
{
    // Convert the SecureString to a BSTR
    IntPtr bstr = Marshal.SecureStringToBSTR(ss);

    // BSTR contains the length of the string in bytes in an
    // Int32 stored in the 4 bytes prior to the BSTR pointer
    int length = Marshal.ReadInt32(bstr, -4);

    // Allocate a byte array to copy the string into
    byte[] bytes = new byte[length];

    // Copy the BSTR to the byte array
    Marshal.Copy(bstr, bytes, 0, length);

    // Immediately destroy the BSTR as we don't need it any more
    Marshal.ZeroFreeBSTR(bstr);

    // Hash the byte array
    byte[] hashed = hash(bytes);

    // Destroy the plaintext copy in the byte array
    for (int i = 0; i < length; i++) { bytes[i] = 0; }

    // Return the hash
    return hashed;
}

これにより、文字列が正しくハッシュされ、提供されたハッシュ関数が適切に動作し、入力のコピーを作成しないと仮定すると、関数が戻るまでにメモリから平文のコピーが正しくスクラブされると思います。スクラブ自体。ここで何か見逃しましたか?

4

4 に答える 4

14

ここで何か見逃しましたか?

はい、かなり基本的なものがあります。ガベージ コレクターがヒープを圧縮するときに取り残された配列のコピーをスクラブすることはできません。Marshal.SecureStringToBSTR(ss) は、BSTR がアンマネージ メモリに割り当てられているため、変更されない信頼性の高いポインターを持つため、問題ありません。言い換えれば、それをスクラブしても問題ありません。

ただし、配列にbyte[] bytes文字列のコピーが含まれており、GC ヒープに割り当てられています。hashed[] 配列を使用すると、ガベージ コレクションを誘発する可能性が高くなります。簡単に回避できますが、もちろん、メモリを割り当ててコレクションを誘導するプロセス内の他のスレッドをほとんど制御できません。または、コードの実行が開始されたときにすでに進行中だったバックグラウンド GC についても同様です。

SecureString のポイントは、ガベージ コレクションされたメモリに文字列の平文コピーを決して持たないことです。それをマネージド配列にコピーすると、その保証に違反しました。このコードを安全にしたい場合は、IntPtr を受け取り、そのポインターのみを読み取る hash() メソッドを作成する必要があります。

ハッシュが別のマシンで計算されたハッシュと一致する必要がある場合、そのマシンが文字列をバイトに変換するために使用するエンコーディングを無視できないことに注意してください。

于 2013-01-12T12:51:44.417 に答える
5

管理されていないCryptoApiまたはCNG関数を使用する可能性は常にあります。SecureStringこれは、メモリ管理を完全に制御できる管理されていないコンシューマーを念頭に置いて設計されていることに注意してください。

C# に固執したい場合は、一時的な配列を固定して、スクラブする機会を得る前に GC が移動するのを防ぐ必要があります。

private static byte[] HashSecureString(SecureString input, Func<byte[], byte[]> hash)
{
    var bstr = Marshal.SecureStringToBSTR(input);
    var length = Marshal.ReadInt32(bstr, -4);
    var bytes = new byte[length];

    var bytesPin = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try {
        Marshal.Copy(bstr, bytes, 0, length);
        Marshal.ZeroFreeBSTR(bstr);

        return hash(bytes);
    } finally {
        for (var i = 0; i < bytes.Length; i++) { 
            bytes[i] = 0; 
        }

        bytesPin.Free();
    }
}
于 2015-04-17T12:12:27.863 に答える
3

ハンスの回答を補完するものとして、ハッシャーの実装方法を提案します。Hans は、アンマネージ文字列へのポインターをハッシュ関数に渡すことを提案していますが、それは、クライアント コード (= ハッシュ関数) がアンマネージ メモリを処理する必要があることを意味します。それは理想的ではありません。

一方、コールバックを次のインターフェイスのインスタンスに置き換えることができます。

interface Hasher {
    void Reinitialize();
    void AddByte(byte b);
    byte[] Result { get; }
}

そうすれば、安全な情報を漏洩することなく、ハッシャーを (少し複雑になりますが) マネージド ランドに完全に実装できます。次にHashSecureString、次のようになります。

private static byte[] HashSecureString(SecureString ss, Hasher hasher) {
    IntPtr bstr = Marshal.SecureStringToBSTR(ss);
    try {
        int length = Marshal.ReadInt32(bstr, -4);

        hasher.Reinitialize();

        for (int i = 0; i < length; i++)
            hasher.AddByte(Marshal.ReadByte(bstr, i));

        return hasher.Result;
    }
    finally {
        Marshal.ZeroFreeBSTR(bstr);
    }
}

finallyハッシュ インスタンスがどんな悪ふざけをしても、アンマネージ メモリが確実にゼロになるようにブロックに注意してください。

インターフェイスを説明するための単純な (そしてあまり役に立たない)Hasher実装を次に示します。

sealed class SingleByteXor : Hasher {
    private readonly byte[] data = new byte[1];

    public void Reinitialize() {
        data[0] = 0;
    }

    public void AddByte(byte b) {
        data[0] ^= b;
    }

    public byte[] Result {
        get { return data; }
    }
}
于 2013-01-12T13:27:52.863 に答える