-
Notifications
You must be signed in to change notification settings - Fork 182
/
AesCcm.cs
170 lines (141 loc) · 7.06 KB
/
AesCcm.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* Copyright (C) 2020 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
*
* You can redistribute this program and/or modify it under the terms of
* the GNU Lesser Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*/
using System;
using System.IO;
using System.Security.Cryptography;
namespace Utilities
{
/// <summary>
/// Implements the Counter with CBC-MAC (CCM) detailed in RFC 3610
/// </summary>
public static class AesCcm
{
private static byte[] CalculateMac(byte[] key, byte[] nonce, byte[] data, byte[] associatedData, int signatureLength)
{
byte[] messageToAuthenticate = BuildB0Block(nonce, true, signatureLength, data.Length);
if (associatedData.Length > 0)
{
if (associatedData.Length >= 65280)
{
throw new NotSupportedException("Associated data length of 65280 or more is not supported");
}
byte[] associatedDataLength = BigEndianConverter.GetBytes((ushort)associatedData.Length);
messageToAuthenticate = ByteUtils.Concatenate(messageToAuthenticate, associatedDataLength);
messageToAuthenticate = ByteUtils.Concatenate(messageToAuthenticate, associatedData);
int associatedDataPaddingLength = (16 - (messageToAuthenticate.Length % 16)) % 16;
messageToAuthenticate = ByteUtils.Concatenate(messageToAuthenticate, new byte[associatedDataPaddingLength]);
}
messageToAuthenticate = ByteUtils.Concatenate(messageToAuthenticate, data);
int dataPaddingLength = (16 - (messageToAuthenticate.Length % 16)) % 16;
messageToAuthenticate = ByteUtils.Concatenate(messageToAuthenticate, new byte[dataPaddingLength]);
byte[] encrypted = AesEncrypt(key, new byte[16], messageToAuthenticate, CipherMode.CBC);
return ByteReader.ReadBytes(encrypted, messageToAuthenticate.Length - 16, signatureLength);
}
public static byte[] Encrypt(byte[] key, byte[] nonce, byte[] data, byte[] associatedData, int signatureLength, out byte[] signature)
{
if (nonce.Length < 7 || nonce.Length > 13)
{
throw new ArgumentException("nonce length must be between 7 and 13 bytes");
}
if (signatureLength < 4 || signatureLength > 16 || (signatureLength % 2 == 1))
{
throw new ArgumentException("signature length must be an even number between 4 and 16 bytes");
}
byte[] keyStream = BuildKeyStream(key, nonce, data.Length);
byte[] mac = CalculateMac(key, nonce, data, associatedData, signatureLength);
signature = ByteUtils.XOR(keyStream, 0, mac, 0, mac.Length);
return ByteUtils.XOR(data, 0, keyStream, 16, data.Length);
}
public static byte[] DecryptAndAuthenticate(byte[] key, byte[] nonce, byte[] encryptedData, byte[] associatedData, byte[] signature)
{
if (nonce.Length < 7 || nonce.Length > 13)
{
throw new ArgumentException("nonce length must be between 7 and 13 bytes");
}
if (signature.Length < 4 || signature.Length > 16 || (signature.Length % 2 == 1))
{
throw new ArgumentException("signature length must be an even number between 4 and 16 bytes");
}
byte[] keyStream = BuildKeyStream(key, nonce, encryptedData.Length);
byte[] data = ByteUtils.XOR(encryptedData, 0, keyStream, 16, encryptedData.Length);
byte[] mac = CalculateMac(key, nonce, data, associatedData, signature.Length);
byte[] expectedSignature = ByteUtils.XOR(keyStream, 0, mac, 0, mac.Length);
if (!ByteUtils.AreByteArraysEqual(expectedSignature, signature))
{
throw new CryptographicException("The computed authentication value did not match the input");
}
return data;
}
private static byte[] BuildKeyStream(byte[] key, byte[] nonce, int dataLength)
{
int paddingLength = (16 - (dataLength % 16) % 16);
int keyStreamLength = 16 + dataLength + paddingLength;
int KeyStreamBlockCount = keyStreamLength / 16;
byte[] keyStreamInput = new byte[keyStreamLength];
for (int index = 0; index < KeyStreamBlockCount; index++)
{
byte[] aBlock = BuildABlock(nonce, index);
ByteWriter.WriteBytes(keyStreamInput, index * 16, aBlock);
}
return AesEncrypt(key, new byte[16], keyStreamInput, CipherMode.ECB);
}
private static byte[] BuildB0Block(byte[] nonce, bool hasAssociatedData, int signatureLength, int messageLength)
{
byte[] b0 = new byte[16];
Array.Copy(nonce, 0, b0, 1, nonce.Length);
int lengthFieldLength = 15 - nonce.Length;
b0[0] = ComputeFlagsByte(hasAssociatedData, signatureLength, lengthFieldLength);
int temp = messageLength;
for (int index = 15; index > 15 - lengthFieldLength; index--)
{
b0[index] = (byte)(temp % 256);
temp /= 256;
}
return b0;
}
private static byte[] BuildABlock(byte[] nonce, int blockIndex)
{
byte[] aBlock = new byte[16];
Array.Copy(nonce, 0, aBlock, 1, nonce.Length);
int lengthFieldLength = 15 - nonce.Length;
aBlock[0] = (byte)(lengthFieldLength - 1);
int temp = blockIndex;
for (int index = 15; index > 15 - lengthFieldLength; index--)
{
aBlock[index] = (byte)(temp % 256);
temp /= 256;
}
return aBlock;
}
private static byte ComputeFlagsByte(bool hasAssociatedData, int signatureLength, int lengthFieldLength)
{
byte flags = 0;
if (hasAssociatedData)
{
flags |= 0x40;
}
flags |= (byte)(lengthFieldLength - 1); // L'
flags |= (byte)(((signatureLength - 2) / 2) << 3); // M'
return flags;
}
private static byte[] AesEncrypt(byte[] key, byte[] iv, byte[] data, CipherMode cipherMode)
{
using (MemoryStream ms = new MemoryStream())
{
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = cipherMode;
aes.Padding = PaddingMode.None;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
}
}
}