1
+ package com .xxdb ;
2
+
3
+ import javax .crypto .Mac ;
4
+ import javax .crypto .SecretKeyFactory ;
5
+ import javax .crypto .spec .PBEKeySpec ;
6
+ import javax .crypto .spec .SecretKeySpec ;
7
+ import java .nio .charset .StandardCharsets ;
8
+ import java .security .InvalidKeyException ;
9
+ import java .security .MessageDigest ;
10
+ import java .security .NoSuchAlgorithmException ;
11
+ import java .security .SecureRandom ;
12
+ import java .security .spec .InvalidKeySpecException ;
13
+ import java .util .Base64 ;
14
+
15
+ public class CryptoUtils {
16
+
17
+ private static final SecureRandom secureRandom = new SecureRandom ();
18
+
19
+ private static final int SHA256_DIGEST_LENGTH = 32 ; // 256 bits = 32 bytes
20
+
21
+ public static String base64Encode (byte [] text , boolean noNewLines ) {
22
+ Base64 .Encoder encoder ;
23
+
24
+ if (noNewLines ) {
25
+ // Use encoder without line breaks
26
+ encoder = Base64 .getEncoder ().withoutPadding ();
27
+ } else {
28
+ // Use standard encoder
29
+ encoder = Base64 .getEncoder ();
30
+ }
31
+
32
+ return encoder .encodeToString (text );
33
+ }
34
+
35
+ public static byte [] base64Decode (String input , boolean noNewLines ) {
36
+ if (input == null )
37
+ throw new IllegalArgumentException ("Input string cannot be null" );
38
+
39
+ Base64 .Decoder decoder ;
40
+
41
+ if (noNewLines ) {
42
+ // Use standard decoder (doesn't handle line breaks)
43
+ decoder = Base64 .getDecoder ();
44
+ } else {
45
+ // Use MIME decoder (handles line breaks)
46
+ decoder = Base64 .getMimeDecoder ();
47
+ }
48
+
49
+ return decoder .decode (input );
50
+ }
51
+
52
+ public static String generateNonce (int length ) {
53
+ // Generate random byte array
54
+ byte [] buffer = new byte [length ];
55
+ secureRandom .nextBytes (buffer );
56
+
57
+ // Base64 encode without line breaks
58
+ return Base64 .getEncoder ().encodeToString (buffer );
59
+ }
60
+
61
+ public static byte [] pbkdf2HmacSha256 (String password , byte [] salt , int iterCount ) {
62
+ try {
63
+ // Create PBEKeySpec
64
+ PBEKeySpec spec = new PBEKeySpec (
65
+ password .toCharArray (),
66
+ salt ,
67
+ iterCount ,
68
+ SHA256_DIGEST_LENGTH * 8 // Convert to bits
69
+ );
70
+
71
+ // Get PBKDF2WithHmacSHA256 instance
72
+ SecretKeyFactory factory = SecretKeyFactory .getInstance ("PBKDF2WithHmacSHA256" );
73
+
74
+ // Generate key
75
+ byte [] saltedPassword = factory .generateSecret (spec ).getEncoded ();
76
+
77
+ // Clear sensitive data
78
+ spec .clearPassword ();
79
+
80
+ return saltedPassword ;
81
+
82
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e ) {
83
+ throw new RuntimeException ("Failed to compute PBKDF2-HMAC-SHA256: " + e .getMessage (), e );
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Calculate client key
89
+ * @param saltedPassword Password processed with PBKDF2
90
+ * @return Client key
91
+ */
92
+ public static byte [] computeClientKey (byte [] saltedPassword ) {
93
+ if (saltedPassword == null || saltedPassword .length != SHA256_DIGEST_LENGTH ) {
94
+ throw new IllegalArgumentException ("Invalid salted password" );
95
+ }
96
+
97
+ // Byte array for "Client Key"
98
+ byte [] clientKeyData = "Client Key" .getBytes ();
99
+
100
+ // Calculate HMAC
101
+ return hmacSha256 (saltedPassword , clientKeyData );
102
+ }
103
+
104
+ /**
105
+ * Calculate stored key (SHA-256 hash of client key)
106
+ * @param clientKey Client key
107
+ * @return Stored key
108
+ */
109
+ public static byte [] computeStoredKey (byte [] clientKey ) {
110
+ if (clientKey == null || clientKey .length != SHA256_DIGEST_LENGTH ) {
111
+ throw new IllegalArgumentException ("Invalid client key" );
112
+ }
113
+
114
+ return sha256 (clientKey );
115
+ }
116
+
117
+ /**
118
+ * Calculate client signature
119
+ * @param storedKey Stored key
120
+ * @param authMessage Authentication message
121
+ * @return Client signature
122
+ */
123
+ public static byte [] computeClientSignature (byte [] storedKey , String authMessage ) {
124
+ if (storedKey == null || storedKey .length != SHA256_DIGEST_LENGTH ) {
125
+ throw new IllegalArgumentException ("Invalid stored key" );
126
+ }
127
+ if (authMessage == null ) {
128
+ throw new IllegalArgumentException ("Auth message cannot be null" );
129
+ }
130
+
131
+ try {
132
+ // Create HMAC-SHA256 instance
133
+ Mac hmac = Mac .getInstance ("HmacSHA256" );
134
+
135
+ // Initialize key
136
+ SecretKeySpec secretKey = new SecretKeySpec (storedKey , "HmacSHA256" );
137
+ hmac .init (secretKey );
138
+
139
+ // Calculate HMAC
140
+ return hmac .doFinal (authMessage .getBytes ());
141
+
142
+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
143
+ throw new RuntimeException ("Failed to compute client signature: " + e .getMessage (), e );
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Calculate proof (XOR operation between client key and client signature)
149
+ * @param clientKey Client key
150
+ * @param clientSignature Client signature
151
+ * @return Proof result
152
+ */
153
+ public static byte [] computeProof (byte [] clientKey , byte [] clientSignature ) {
154
+ if (clientKey == null || clientSignature == null ||
155
+ clientKey .length != clientSignature .length ) {
156
+ throw new IllegalArgumentException ("Invalid input arrays" );
157
+ }
158
+
159
+ byte [] proof = new byte [clientKey .length ];
160
+ for (int i = 0 ; i < clientKey .length ; i ++) {
161
+ proof [i ] = (byte )(clientKey [i ] ^ clientSignature [i ]);
162
+ }
163
+ return proof ;
164
+ }
165
+
166
+ /**
167
+ * Calculate server key
168
+ * @param saltedPassword Password processed with PBKDF2
169
+ * @return Server key
170
+ */
171
+ public static byte [] computeServerKey (byte [] saltedPassword ) {
172
+ if (saltedPassword == null || saltedPassword .length != SHA256_DIGEST_LENGTH ) {
173
+ throw new IllegalArgumentException ("Invalid salted password" );
174
+ }
175
+
176
+ try {
177
+ // Create HMAC-SHA256 instance
178
+ Mac hmac = Mac .getInstance ("HmacSHA256" );
179
+
180
+ // Initialize key
181
+ SecretKeySpec secretKey = new SecretKeySpec (saltedPassword , "HmacSHA256" );
182
+ hmac .init (secretKey );
183
+
184
+ // Calculate HMAC
185
+ return hmac .doFinal ("Server Key" .getBytes (StandardCharsets .UTF_8 ));
186
+
187
+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
188
+ throw new RuntimeException ("Failed to compute server key: " + e .getMessage (), e );
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Calculate server signature
194
+ * @param serverKey Server key
195
+ * @param authMessage Authentication message
196
+ * @return Server signature
197
+ */
198
+ public static byte [] computeServerSignature (byte [] serverKey , String authMessage ) {
199
+ if (serverKey == null || serverKey .length != SHA256_DIGEST_LENGTH ) {
200
+ throw new IllegalArgumentException ("Invalid server key" );
201
+ }
202
+ if (authMessage == null ) {
203
+ throw new IllegalArgumentException ("Auth message cannot be null" );
204
+ }
205
+
206
+ try {
207
+ // Create HMAC-SHA256 instance
208
+ Mac hmac = Mac .getInstance ("HmacSHA256" );
209
+
210
+ // Initialize key
211
+ SecretKeySpec secretKey = new SecretKeySpec (serverKey , "HmacSHA256" );
212
+ hmac .init (secretKey );
213
+
214
+ // Calculate HMAC
215
+ return hmac .doFinal (authMessage .getBytes (StandardCharsets .UTF_8 ));
216
+
217
+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
218
+ throw new RuntimeException ("Failed to compute server signature: " + e .getMessage (), e );
219
+ }
220
+ }
221
+
222
+
223
+
224
+ /**
225
+ * Calculate HMAC-SHA256
226
+ * @param key HMAC key
227
+ * @param data Data to calculate HMAC for
228
+ * @return HMAC result
229
+ */
230
+ public static byte [] hmacSha256 (byte [] key , byte [] data ) {
231
+ try {
232
+ // Create HMAC-SHA256 instance
233
+ Mac hmac = Mac .getInstance ("HmacSHA256" );
234
+
235
+ // Initialize key
236
+ SecretKeySpec secretKey = new SecretKeySpec (key , "HmacSHA256" );
237
+ hmac .init (secretKey );
238
+
239
+ // Calculate HMAC
240
+ return hmac .doFinal (data );
241
+
242
+ } catch (NoSuchAlgorithmException | InvalidKeyException e ) {
243
+ throw new RuntimeException ("Failed to compute HMAC-SHA256: " + e .getMessage (), e );
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Calculate SHA-256 hash
249
+ * @param data Data to hash
250
+ * @return Hash result
251
+ */
252
+ public static byte [] sha256 (byte [] data ) {
253
+ try {
254
+ MessageDigest digest = MessageDigest .getInstance ("SHA-256" );
255
+ return digest .digest (data );
256
+ } catch (NoSuchAlgorithmException e ) {
257
+ throw new RuntimeException ("SHA-256 algorithm not available" , e );
258
+ }
259
+ }
260
+ }
0 commit comments