C# TLS 1.1 实现

标签 c# .net ssl handshake

一段时间以来,我一直在拼命地尝试在我的应用程序中实现 TLS 1.1。这背后的原因是使用了 SocketType.Raw 套接字,因此我无法使用 SslStream 或其他更高级别的类。

到目前为止,我一直停留在 TLS 握手协议(protocol)中的 Finished 消息 - 继续在服务器响应中接收 bad_record_mac(20)。密码套件是 0x0005 - TLS_RSA_WITH_RC4_128_SHA。

下面是一些关于正在发生的事情的示例代码:

    byte[] client_random, server_random = new byte[28];
    byte[] pre_master_secret, master_secret;
    byte[] client_write_MAC_secret, server_write_MAC_secret, client_write_key, server_write_key;
    byte[] handshake_messages, verify_data;

    RSACryptoServiceProvider rsa;
    List<X509Certificate2> certificates = new List<X509Certificate2>();

    //certificates are added
    rsa = (RSACryptoServiceProvider)certificates.First().PublicKey.Key;

    private byte[] ClientKeyExchange()
    {
        pre_master_secret = new byte[48];
        (new Random()).NextBytes(pre_master_secret);
        
        //version 0302 for TLS 1.1
        pre_master_secret[0] = 3;
        pre_master_secret[1] = 2;

        byte[] cryptedData = rsa.Encrypt(pre_master_secret, false);
        //"1603020086"
        string tmp_string = "100000820080" + Utils.BitConverter.ToString(cryptedData).Replace("-", "");
        AddHandShakeData(Utils.BitConverter.StringToByteArray(tmp_string));
        tmp_string =
            "1603020086"
            + tmp_string
            + "140302000101" //Cipher Change Spec
            + "";

        return Utils.BitConverter.StringToByteArray(tmp_string);
    }

    private void ComputeMasterSecret()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("master secret", Encoding.ASCII));

        byte[] seed = new byte[client_random.Length + server_random.Length];
        Buffer.BlockCopy(client_random, 0, seed, 0, client_random.Length);
        Buffer.BlockCopy(server_random, 0, seed, client_random.Length, server_random.Length);

        master_secret = PRF(pre_master_secret, label, seed, 48);
    }

    private void ComputeKeys()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("key expansion", Encoding.ASCII));

        byte[] seed = new byte[client_random.Length + server_random.Length];
        Buffer.BlockCopy(client_random, 0, seed, 0, client_random.Length);
        Buffer.BlockCopy(server_random, 0, seed, client_random.Length, server_random.Length);

        byte[] key_material = PRF(master_secret, label, seed, 72);

        client_write_MAC_secret = new byte[20];
        Buffer.BlockCopy(key_material, 0, client_write_MAC_secret, 0, 20);
        server_write_MAC_secret = new byte[20];
        Buffer.BlockCopy(key_material, 20, server_write_MAC_secret, 0, 20);
        client_write_key = new byte[16];
        Buffer.BlockCopy(key_material, 40, client_write_key, 0, 16);
        server_write_key = new byte[16];
        Buffer.BlockCopy(key_material, 56, server_write_key, 0, 16);
    }

    private void ComputeVerifyData()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("client finished", Encoding.ASCII));

        SHA1 sha1 = SHA1.Create();
        MD5 md5 = MD5.Create();
        md5.ComputeHash(handshake_messages);
        sha1.ComputeHash(handshake_messages);

        byte[] seed = new byte[md5.HashSize / 8 + sha1.HashSize / 8];
        Buffer.BlockCopy(md5.Hash, 0, seed, 0, md5.HashSize / 8);
        Buffer.BlockCopy(sha1.Hash, 0, seed, md5.HashSize / 8, sha1.HashSize / 8);

        verify_data = PRF(master_secret, label, seed, 12);
    }

    private byte[] PRF(byte[] secret, byte[] label, byte[] seed, int output_size)
    {
        int md5_iterations = (int)Math.Ceiling((double)output_size / 16),
            sha1_iterations = (int)Math.Ceiling((double)output_size / 20);

        byte[] md5_data = new byte[output_size],
               sha1_data = new byte[output_size];

        //особое колдунство для нечетного числа
        byte[] secret_1 = new byte[(int)Math.Ceiling((double)secret.Length / 2)],
               secret_2 = new byte[(int)Math.Ceiling((double)secret.Length / 2)];
        Buffer.BlockCopy(secret, 0, secret_1, 0, secret_1.Length);
        Buffer.BlockCopy(secret, secret.Length / 2, secret_2, 0, secret_2.Length);

        byte[] A = new byte[label.Length + seed.Length];
        Buffer.BlockCopy(label, 0, A, 0, label.Length);
        Buffer.BlockCopy(seed, 0, A, label.Length, seed.Length);

        byte[] tmp = new byte[md5_iterations * 16];

        //A(1) ?
        //A = P_MD5(secret_1, A);

        for (int i = 0; i < md5_iterations; i++)
        {
            A = P_MD5(secret_1, A);
            Buffer.BlockCopy(A, 0, tmp, i * A.Length, A.Length);
        }
        Buffer.BlockCopy(tmp, 0, md5_data, 0, md5_data.Length); //output_size = md5_data.Length

        tmp = new byte[sha1_iterations * 20];

        //does it have to start with A(1) ?
        //A = P_SHA1(secret_2, A);


        for (int i = 0; i < sha1_iterations; i++)
        {
            A = P_SHA1(secret_2, A);
            Buffer.BlockCopy(A, 0, tmp, i * A.Length, A.Length);
        }
        Buffer.BlockCopy(tmp, 0, sha1_data, 0, sha1_data.Length); //output_size = sha1_data.Length

        for (int i = 0; i < output_size; i++)
            md5_data[i] = (byte)(md5_data[i] ^ sha1_data[i]);

        return md5_data;
    }

    private byte[] P_MD5(byte[] secret /*это ключ?*/, byte[] seed /*это дата?*/)
    {
        HMACMD5 HMD5 = new HMACMD5(secret);
        HMD5.ComputeHash(seed);
        return HMD5.Hash;
    }

    private byte[] P_SHA1(byte[] secret /*это ключ?*/, byte[] seed /*это дата?*/)
    {
        HMACSHA1 HSHA1 = new HMACSHA1(secret);
        HSHA1.ComputeHash(seed);
        return HSHA1.Hash;
    }

    private byte[] MAC(byte[] secret, byte[] data)
    {
        byte[] secret_64 = new byte[64];
        Buffer.BlockCopy(secret, 0, secret_64, 0, secret.Length);

        for (int i = 0; i < 64; i++)
            secret_64[i] = (byte)(secret_64[i] ^ (byte)54);

        byte[] xor_output_data = new byte[64 + data.Length];
        Buffer.BlockCopy(secret_64, 0, xor_output_data, 0, 64);
        Buffer.BlockCopy(data, 0, xor_output_data, 64, data.Length);

        SHA1 sha1 = SHA1.Create();
        sha1.ComputeHash(xor_output_data);

        secret_64 = new byte[64];
        Buffer.BlockCopy(secret, 0, secret_64, 0, secret.Length);

        for (int i = 0; i < 64; i++)
            secret_64[i] = (byte)(secret_64[i] ^ (byte)92);

        xor_output_data = new byte[64 + sha1.HashSize / 8];
        Buffer.BlockCopy(secret_64, 0, xor_output_data, 0, 64);
        Buffer.BlockCopy(sha1.Hash, 0, xor_output_data, 64, sha1.HashSize / 8);

        sha1.ComputeHash(xor_output_data);

        return sha1.Hash;
    }

    public void RC4(ref Byte[] bytes, Byte[] key)
    {
        Byte[] s = new Byte[128];
        Byte[] k = new Byte[128];
        Byte temp;
        int i, j;

        for (i = 0; i < 128; i++)
        {
            s[i] = (Byte)i;
            k[i] = key[i % key.GetLength(0)];
        }

        j = 0;
        for (i = 0; i < 128; i++)
        {
            j = (j + s[i] + k[i]) % 128;
            temp = s[i];
            s[i] = s[j];
            s[j] = temp;
        }

        i = j = 0;
        for (int x = 0; x < bytes.GetLength(0); x++)
        {
            i = (i + 1) % 128;
            j = (j + s[i]) % 128;
            temp = s[i];
            s[i] = s[j];
            s[j] = temp;
            int t = (s[i] + s[j]) % 128;
            bytes[x] ^= s[t];
        }
    }

显然,阅读这么多代码并不是打发时间的最佳方式,所以我想补充一些问题,希望能对您有所帮助:

HMAC_MD5 和 HMAC_SHA1 - 在 .Net 实现中,它们采用 key 并采用输入 byte[] 来计算散列。根据RFC4346 : 首先,我们定义一个数据扩展函数,P_hash(secret, data) 它使用单个散列函数将 secret 和种子扩展为 任意数量的输出:

   P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
                          HMAC_hash(secret, A(2) + seed) +
                          HMAC_hash(secret, A(3) + seed) + ...

   Where + indicates concatenation.

   A() is defined as:

   A(0) = seed
   A(i) = HMAC_hash(secret, A(i-1))

什么是种子?我们从中计算哈希值的数据吗? secret =关键,到目前为止我明白了。另外,我们是否以 A(1) 开始 P_hash?

提前致谢!

最佳答案

种子是 Client_random + Server_random。您需要从握手的前面步骤中保留它们,即 client_hello 和 server_hello。

A(0) = seed 
A(i) = HMAC_hash(secret,A(i-1)) for i>0

A()函数的输出由A(1)、A(2)、A(3)...组成

关于C# TLS 1.1 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17582210/

相关文章:

performance - 使用 SSL 避免登陆页面重定向

ssl - Squeak-5.2 无法连接到 https ://github. com/

ssl - 在 nginx 的主域而不是子域中应用 SSL 和 www

c# - Windows.Forms.MouseButtons 中的奇数枚举值

c# - 显示某些基于查询字符串的 HTML

c#一道简单的调试题

javascript - 下载 asp.net 后触发 javascript 函数

c# - 由于超时导致异常后,我可以继续使用 NetworkStream 吗?

c# - 从pdf文件中读取超链接

c# - 如何从另一个项目中调用一个Form