40. 세션 암호화 스트림의 nonce/IV 재사용 (C#)

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// [시나리오] 게임 세션 패킷 암호화 (서버-클라)
//
// 로그인 후 클라·서버는 세션 키를 공유하고, 이후 모든 게임 패킷을
// 스트림 암호(AES-CTR 류: keystream = E(key, nonce) 를 평문과 XOR)로
// 암호화해 주고받는다. 각 패킷마다 nonce 를 하나씩 증가시켜 사용한다.
//
// 재접속(reconnect)이나 세션 재협상 시 SessionCrypto 를 다시 초기화한다.
//
// [요구사항]
//  - 같은 (key, nonce) 조합으로 서로 다른 평문을 두 번 암호화해서는 안 된다.
//  - 송신 스트림과 수신 스트림, 재접속 전/후가 이 불변식을 깨지 않아야 한다.
//
// [과제]
//  아래 SessionCrypto 를 리뷰하라. (A)(B) 지점의 동작이 위 불변식에
//  어떤 영향을 주는지, 공격자가 무엇을 할 수 있는지 재현과 함께 설명하고
//  수정안을 제시하라. (Encrypt/Decrypt 의 블록 함수 자체는 올바르다고 가정)

using System;

public class SessionCrypto
{
    private byte[] _key;
    private ulong _nonce;               // 패킷마다 증가

    public void InitSession(byte[] sessionKey)
    {
        _key = sessionKey;
        // (A)
        _nonce = 0;
    }

    // 재접속/재협상 시 호출 (키는 그대로 재사용, 카운터만 리셋)
    public void ResetForReconnect()
    {
        // (A)
        _nonce = 0;
    }

    public byte[] EncryptOutbound(ReadOnlySpan<byte> plain)
    {
        // (B)
        var keystream = BlockCipher.Ctr(_key, _nonce, plain.Length);
        _nonce++;
        return Xor(plain, keystream);
    }

    public byte[] DecryptInbound(ReadOnlySpan<byte> cipher)
    {
        var keystream = BlockCipher.Ctr(_key, _nonce, cipher.Length);
        _nonce++;
        return Xor(cipher, keystream);
    }

    private static byte[] Xor(ReadOnlySpan<byte> a, byte[] k)
    {
        var o = new byte[a.Length];
        for (int i = 0; i < a.Length; i++) o[i] = (byte)(a[i] ^ k[i]);
        return o;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.