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