11using System ;
22using System . IO ;
3+ using System . Linq ;
34using System . Security . Cryptography ;
5+ using System . Text ;
46using Mastercard . Developer . ClientEncryption . Core . Utils ;
57using Mastercard . Developer . ClientEncryption . Core . Encryption . JWE ;
68
79namespace Mastercard . Developer . ClientEncryption . Core . Encryption . AES
810{
11+ internal class AesCbcAuthenticated
12+ {
13+ public byte [ ] Ciphertext { get ; private set ; }
14+ public byte [ ] AuthTag { get ; private set ; }
15+
16+ internal AesCbcAuthenticated ( byte [ ] ciphertext , byte [ ] authTag )
17+ {
18+ Ciphertext = ciphertext ;
19+ AuthTag = authTag ;
20+ }
21+ }
22+
923 internal static class AesCbc
1024 {
11- public static byte [ ] Decrypt ( byte [ ] secretKeyBytes , JweObject jweObject )
25+ public static byte [ ] Decrypt ( byte [ ] secretKeyBytes , JweObject jweObject , bool enableHmacVerification )
1226 {
13- // Extract the encryption key
14- byte [ ] aesKey = new byte [ 16 ] ;
15- Array . Copy ( secretKeyBytes , 16 , aesKey , 0 , aesKey . Length ) ;
27+ // Determine key sizes based on the total secret key length
28+ // A128CBC-HS256: 32 bytes (16 for HMAC, 16 for AES)
29+ // A192CBC-HS384: 48 bytes (24 for HMAC, 24 for AES)
30+ // A256CBC-HS512: 64 bytes (32 for HMAC, 32 for AES)
31+ int keyLength = secretKeyBytes . Length / 2 ;
32+
33+ // Extract HMAC key (first half) and encryption key (second half)
34+ byte [ ] hmacKey = new byte [ keyLength ] ;
35+ byte [ ] aesKey = new byte [ keyLength ] ;
36+ Array . Copy ( secretKeyBytes , 0 , hmacKey , 0 , keyLength ) ;
37+ Array . Copy ( secretKeyBytes , keyLength , aesKey , 0 , keyLength ) ;
1638
39+ // Decode values needed for both HMAC and decryption
40+ byte [ ] authTag = Base64Utils . URLDecode ( jweObject . AuthTag ) ;
41+ byte [ ] iv = Base64Utils . URLDecode ( jweObject . Iv ) ;
42+ byte [ ] ciphertext = Base64Utils . URLDecode ( jweObject . CipherText ) ;
43+
44+ // Verify HMAC only if enabled
45+ if ( enableHmacVerification )
46+ {
47+ byte [ ] aad = Encoding . ASCII . GetBytes ( jweObject . RawHeader ) ;
48+
49+ if ( ! VerifyHmac ( hmacKey , aad , iv , ciphertext , authTag ) )
50+ {
51+ throw new EncryptionException ( "HMAC verification failed" ) ;
52+ }
53+ }
54+
55+ // Decrypt
1756 byte [ ] plaintext ;
1857 using ( var aes = Aes . Create ( ) )
1958 {
2059 aes . Key = aesKey ;
2160 aes . Mode = CipherMode . CBC ;
2261 aes . Padding = PaddingMode . PKCS7 ;
23- aes . IV = Base64Utils . URLDecode ( jweObject . Iv ) ;
62+ aes . IV = iv ;
2463
25- byte [ ] ciphertext = Base64Utils . URLDecode ( jweObject . CipherText ) ;
2664 using ( var decryptor = aes . CreateDecryptor ( ) )
2765 {
2866 using ( var memoryStream = new MemoryStream ( ciphertext ) )
@@ -43,5 +81,120 @@ public static byte[] Decrypt(byte[] secretKeyBytes, JweObject jweObject)
4381 }
4482 return plaintext ;
4583 }
84+
85+ internal static AesCbcAuthenticated Encrypt ( byte [ ] secretKeyBytes , byte [ ] iv , byte [ ] plaintext , byte [ ] aad , bool enableHmacGeneration )
86+ {
87+ // Determine key sizes based on the total secret key length
88+ int keyLength = secretKeyBytes . Length / 2 ;
89+
90+ // Extract HMAC key (first half) and encryption key (second half)
91+ byte [ ] hmacKey = new byte [ keyLength ] ;
92+ byte [ ] aesKey = new byte [ keyLength ] ;
93+ Array . Copy ( secretKeyBytes , 0 , hmacKey , 0 , keyLength ) ;
94+ Array . Copy ( secretKeyBytes , keyLength , aesKey , 0 , keyLength ) ;
95+
96+ // Encrypt
97+ byte [ ] ciphertext ;
98+ using ( var aes = Aes . Create ( ) )
99+ {
100+ aes . Key = aesKey ;
101+ aes . Mode = CipherMode . CBC ;
102+ aes . Padding = PaddingMode . PKCS7 ;
103+ aes . IV = iv ;
104+
105+ using ( var encryptor = aes . CreateEncryptor ( ) )
106+ {
107+ using ( var memoryStream = new MemoryStream ( ) )
108+ {
109+ using ( var cryptoStream = new CryptoStream ( memoryStream , encryptor , CryptoStreamMode . Write ) )
110+ {
111+ cryptoStream . Write ( plaintext , 0 , plaintext . Length ) ;
112+ cryptoStream . FlushFinalBlock ( ) ;
113+ ciphertext = memoryStream . ToArray ( ) ;
114+ }
115+ }
116+ }
117+ }
118+
119+ // Compute HMAC only if enabled
120+ byte [ ] authTag ;
121+ if ( enableHmacGeneration )
122+ {
123+ byte [ ] fullHmac = ComputeHmac ( hmacKey , aad , iv , ciphertext ) ;
124+ // Truncate to half the length for the authentication tag (same as keyLength)
125+ authTag = new byte [ keyLength ] ;
126+ Array . Copy ( fullHmac , 0 , authTag , 0 , keyLength ) ;
127+ }
128+ else
129+ {
130+ authTag = new byte [ 0 ] ; // Empty auth tag when HMAC is disabled
131+ }
132+
133+ return new AesCbcAuthenticated ( ciphertext , authTag ) ;
134+ }
135+
136+ private static bool VerifyHmac ( byte [ ] hmacKey , byte [ ] aad , byte [ ] iv , byte [ ] ciphertext , byte [ ] authTag )
137+ {
138+ byte [ ] expectedTag = ComputeHmac ( hmacKey , aad , iv , ciphertext ) ;
139+
140+ // Truncate to half the length for the authentication tag
141+ int tagLength = hmacKey . Length ;
142+ byte [ ] truncatedExpectedTag = new byte [ tagLength ] ;
143+ Array . Copy ( expectedTag , 0 , truncatedExpectedTag , 0 , tagLength ) ;
144+
145+ // Constant-time comparison
146+ if ( authTag . Length != truncatedExpectedTag . Length )
147+ {
148+ return false ;
149+ }
150+
151+ int result = 0 ;
152+ for ( int i = 0 ; i < authTag . Length ; i ++ )
153+ {
154+ result |= authTag [ i ] ^ truncatedExpectedTag [ i ] ;
155+ }
156+
157+ return result == 0 ;
158+ }
159+
160+ private static byte [ ] ComputeHmac ( byte [ ] hmacKey , byte [ ] aad , byte [ ] iv , byte [ ] ciphertext )
161+ {
162+ // Construct Additional Authenticated Data (AAD) length in bits as 64-bit big-endian
163+ long aadLengthBits = ( long ) aad . Length * 8 ;
164+ byte [ ] aadLength = BitConverter . GetBytes ( aadLengthBits ) ;
165+ if ( BitConverter . IsLittleEndian )
166+ {
167+ Array . Reverse ( aadLength ) ;
168+ }
169+
170+ // Concatenate: AAD || IV || Ciphertext || AAD Length
171+ var hmacInput = new MemoryStream ( ) ;
172+ hmacInput . Write ( aad , 0 , aad . Length ) ;
173+ hmacInput . Write ( iv , 0 , iv . Length ) ;
174+ hmacInput . Write ( ciphertext , 0 , ciphertext . Length ) ;
175+ hmacInput . Write ( aadLength , 0 , aadLength . Length ) ;
176+
177+ // Determine HMAC algorithm based on key length
178+ HMAC hmac ;
179+ switch ( hmacKey . Length )
180+ {
181+ case 16 : // HS256
182+ hmac = new HMACSHA256 ( hmacKey ) ;
183+ break ;
184+ case 24 : // HS384
185+ hmac = new HMACSHA384 ( hmacKey ) ;
186+ break ;
187+ case 32 : // HS512
188+ hmac = new HMACSHA512 ( hmacKey ) ;
189+ break ;
190+ default :
191+ throw new EncryptionException ( $ "Unsupported HMAC key length: { hmacKey . Length } ") ;
192+ }
193+
194+ using ( hmac )
195+ {
196+ return hmac . ComputeHash ( hmacInput . ToArray ( ) ) ;
197+ }
198+ }
46199 }
47200}
0 commit comments