|
23 | 23 | import org.tron.common.crypto.SignUtils; |
24 | 24 | import org.tron.common.utils.ByteArray; |
25 | 25 | import org.tron.common.utils.StringUtil; |
26 | | -import org.tron.core.config.args.Args; |
27 | 26 | import org.tron.core.exception.CipherException; |
28 | 27 |
|
29 | 28 | /** |
|
48 | 47 | */ |
49 | 48 | public class Wallet { |
50 | 49 |
|
51 | | - protected static final String AES_128_CTR = "pbkdf2"; |
| 50 | + // KDF identifiers used in the Web3 Secret Storage "kdf" field. |
| 51 | + // The old name "AES_128_CTR" was misleading — the value is the PBKDF2 KDF |
| 52 | + // identifier, not the cipher (CIPHER below). The inner class name |
| 53 | + // `WalletFile.Aes128CtrKdfParams` is kept for wire-format/Jackson-subtype |
| 54 | + // backward compatibility even though it also reflects the same history. |
| 55 | + protected static final String PBKDF2 = "pbkdf2"; |
52 | 56 | protected static final String SCRYPT = "scrypt"; |
53 | 57 | private static final int N_LIGHT = 1 << 12; |
54 | 58 | private static final int P_LIGHT = 6; |
@@ -168,8 +172,8 @@ private static byte[] generateMac(byte[] derivedKey, byte[] cipherText) { |
168 | 172 | return Hash.sha3(result); |
169 | 173 | } |
170 | 174 |
|
171 | | - public static SignInterface decrypt(String password, WalletFile walletFile) |
172 | | - throws CipherException { |
| 175 | + public static SignInterface decrypt(String password, WalletFile walletFile, |
| 176 | + boolean ecKey) throws CipherException { |
173 | 177 |
|
174 | 178 | validate(walletFile); |
175 | 179 |
|
@@ -205,32 +209,79 @@ public static SignInterface decrypt(String password, WalletFile walletFile) |
205 | 209 |
|
206 | 210 | byte[] derivedMac = generateMac(derivedKey, cipherText); |
207 | 211 |
|
208 | | - if (!Arrays.equals(derivedMac, mac)) { |
| 212 | + if (!java.security.MessageDigest.isEqual(derivedMac, mac)) { |
209 | 213 | throw new CipherException("Invalid password provided"); |
210 | 214 | } |
211 | 215 |
|
212 | 216 | byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); |
213 | 217 | byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText); |
214 | 218 |
|
215 | | - return SignUtils.fromPrivate(privateKey, Args.getInstance().isECKeyCryptoEngine()); |
216 | | - } |
| 219 | + SignInterface keyPair = SignUtils.fromPrivate(privateKey, ecKey); |
| 220 | + |
| 221 | + // Enforce address consistency: if the keystore declares an address, it MUST match |
| 222 | + // the address derived from the decrypted private key. Prevents address spoofing |
| 223 | + // where a crafted keystore displays one address but encrypts a different key. |
| 224 | + String declared = walletFile.getAddress(); |
| 225 | + if (declared != null && !declared.isEmpty()) { |
| 226 | + String derived = StringUtil.encode58Check(keyPair.getAddress()); |
| 227 | + if (!declared.equals(derived)) { |
| 228 | + throw new CipherException( |
| 229 | + "Keystore address mismatch: file declares " + declared |
| 230 | + + " but private key derives " + derived); |
| 231 | + } |
| 232 | + } |
217 | 233 |
|
218 | | - static void validate(WalletFile walletFile) throws CipherException { |
219 | | - WalletFile.Crypto crypto = walletFile.getCrypto(); |
| 234 | + return keyPair; |
| 235 | + } |
220 | 236 |
|
| 237 | + /** |
| 238 | + * Returns a description of the first schema violation found in |
| 239 | + * {@code walletFile}, or {@code null} if the file matches the supported |
| 240 | + * V3 keystore shape (current version, known cipher, known KDF). |
| 241 | + * |
| 242 | + * <p>Shared by {@link #validate(WalletFile)} (which throws the message) |
| 243 | + * and {@link #isValidKeystoreFile(WalletFile)} (which returns boolean |
| 244 | + * for discovery-style filtering). |
| 245 | + */ |
| 246 | + private static String validationError(WalletFile walletFile) { |
221 | 247 | if (walletFile.getVersion() != CURRENT_VERSION) { |
222 | | - throw new CipherException("Wallet version is not supported"); |
| 248 | + return "Wallet version is not supported"; |
223 | 249 | } |
224 | | - |
225 | | - if (!crypto.getCipher().equals(CIPHER)) { |
226 | | - throw new CipherException("Wallet cipher is not supported"); |
| 250 | + WalletFile.Crypto crypto = walletFile.getCrypto(); |
| 251 | + if (crypto == null) { |
| 252 | + return "Missing crypto section"; |
| 253 | + } |
| 254 | + String cipher = crypto.getCipher(); |
| 255 | + if (cipher == null || !cipher.equals(CIPHER)) { |
| 256 | + return "Wallet cipher is not supported"; |
227 | 257 | } |
| 258 | + String kdf = crypto.getKdf(); |
| 259 | + if (kdf == null || (!kdf.equals(PBKDF2) && !kdf.equals(SCRYPT))) { |
| 260 | + return "KDF type is not supported"; |
| 261 | + } |
| 262 | + return null; |
| 263 | + } |
228 | 264 |
|
229 | | - if (!crypto.getKdf().equals(AES_128_CTR) && !crypto.getKdf().equals(SCRYPT)) { |
230 | | - throw new CipherException("KDF type is not supported"); |
| 265 | + static void validate(WalletFile walletFile) throws CipherException { |
| 266 | + String error = validationError(walletFile); |
| 267 | + if (error != null) { |
| 268 | + throw new CipherException(error); |
231 | 269 | } |
232 | 270 | } |
233 | 271 |
|
| 272 | + /** |
| 273 | + * Returns {@code true} iff {@code walletFile} has the shape of a |
| 274 | + * decryptable V3 keystore: non-null address, supported version, non-null |
| 275 | + * crypto section with a supported cipher and KDF. Intended for |
| 276 | + * discovery-style filtering (e.g. listing or duplicate detection) where |
| 277 | + * we want to skip JSON stubs that would later fail {@link #validate}. |
| 278 | + */ |
| 279 | + public static boolean isValidKeystoreFile(WalletFile walletFile) { |
| 280 | + return walletFile != null |
| 281 | + && walletFile.getAddress() != null |
| 282 | + && validationError(walletFile) == null; |
| 283 | + } |
| 284 | + |
234 | 285 | public static byte[] generateRandomBytes(int size) { |
235 | 286 | byte[] bytes = new byte[size]; |
236 | 287 | new SecureRandom().nextBytes(bytes); |
|
0 commit comments