|
| 1 | +package com.example.bip39.crypto; |
| 2 | + |
| 3 | +import com.example.bip39.error.Bip39ErrorCode; |
| 4 | +import com.example.bip39.error.Bip39Exception; |
| 5 | +import com.example.bip39.util.Bip39Constants; |
| 6 | +import java.security.InvalidKeyException; |
| 7 | +import java.security.NoSuchAlgorithmException; |
| 8 | +import java.util.Arrays; |
| 9 | +import java.util.Objects; |
| 10 | +import javax.crypto.Mac; |
| 11 | +import javax.crypto.spec.SecretKeySpec; |
| 12 | + |
| 13 | +public final class Pbkdf2HmacSha512 { |
| 14 | + |
| 15 | + private static final String HMAC_SHA512 = "HmacSHA512"; |
| 16 | + |
| 17 | + private Pbkdf2HmacSha512() {} |
| 18 | + |
| 19 | + public static byte[] derive(byte[] passwordBytes, byte[] saltBytes) { |
| 20 | + Objects.requireNonNull(passwordBytes, "passwordBytes must not be null"); |
| 21 | + Objects.requireNonNull(saltBytes, "saltBytes must not be null"); |
| 22 | + |
| 23 | + try { |
| 24 | + Mac mac = Mac.getInstance(HMAC_SHA512); |
| 25 | + mac.init(new SecretKeySpec(passwordBytes, HMAC_SHA512)); |
| 26 | + return deriveKey( |
| 27 | + mac, saltBytes, Bip39Constants.PBKDF2_ITERATIONS, Bip39Constants.SEED_LEN_BYTES); |
| 28 | + } catch (NoSuchAlgorithmException | InvalidKeyException exception) { |
| 29 | + throw new Bip39Exception( |
| 30 | + Bip39ErrorCode.ERR_PBKDF2_FAILURE, "PBKDF2-HMAC-SHA512 is unavailable", exception); |
| 31 | + } |
| 32 | + } |
| 33 | + |
| 34 | + private static byte[] deriveKey( |
| 35 | + Mac mac, byte[] saltBytes, int iterations, int derivedKeyLengthBytes) { |
| 36 | + int blockLengthBytes = mac.getMacLength(); |
| 37 | + int blockCount = (derivedKeyLengthBytes + blockLengthBytes - 1) / blockLengthBytes; |
| 38 | + byte[] derivedKey = new byte[blockCount * blockLengthBytes]; |
| 39 | + |
| 40 | + for (int blockIndex = 1; blockIndex <= blockCount; blockIndex++) { |
| 41 | + byte[] block = deriveBlock(mac, saltBytes, iterations, blockIndex); |
| 42 | + System.arraycopy(block, 0, derivedKey, (blockIndex - 1) * blockLengthBytes, block.length); |
| 43 | + } |
| 44 | + |
| 45 | + return Arrays.copyOf(derivedKey, derivedKeyLengthBytes); |
| 46 | + } |
| 47 | + |
| 48 | + private static byte[] deriveBlock(Mac mac, byte[] saltBytes, int iterations, int blockIndex) { |
| 49 | + byte[] input = Arrays.copyOf(saltBytes, saltBytes.length + Integer.BYTES); |
| 50 | + writeIntBigEndian(input, saltBytes.length, blockIndex); |
| 51 | + |
| 52 | + byte[] iterationResult = mac.doFinal(input); |
| 53 | + byte[] block = iterationResult.clone(); |
| 54 | + |
| 55 | + for (int iteration = 1; iteration < iterations; iteration++) { |
| 56 | + iterationResult = mac.doFinal(iterationResult); |
| 57 | + xorInto(block, iterationResult); |
| 58 | + } |
| 59 | + |
| 60 | + return block; |
| 61 | + } |
| 62 | + |
| 63 | + private static void xorInto(byte[] target, byte[] source) { |
| 64 | + for (int index = 0; index < target.length; index++) { |
| 65 | + target[index] ^= source[index]; |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + private static void writeIntBigEndian(byte[] bytes, int offset, int value) { |
| 70 | + bytes[offset] = (byte) (value >>> 24); |
| 71 | + bytes[offset + 1] = (byte) (value >>> 16); |
| 72 | + bytes[offset + 2] = (byte) (value >>> 8); |
| 73 | + bytes[offset + 3] = (byte) value; |
| 74 | + } |
| 75 | +} |
0 commit comments