Reputation: 3608
I need to wrap private value with AESWrap
. I have a problem with this operation because of length of my private value string (key to be wrapped).
This is my implementation:
final byte[] kek = // ... generate SHA-256 key via `PBKDF2WithHmacSHA256`
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AESWrap", "SunJCE");
c.init(Cipher.WRAP_MODE, sKey);
byte[] bytes = privateValue.getBytes();
SecretKeySpec wk = new SecretKeySpec(bytes, "AES");
byte[] result = c.wrap(wk);
Since SunJCE
provider doesn't support any padding for key wrapping so private value should be multiples of 8 bytes and this is my problem I need to solve.
Question: How to solve this situation when private value has not sufficient length. Is there some recommended way how to do padding on my own?
P.S. I would like to avoid external libraries like BC etc.
With respect to @Maarten's answer I created this implementation. It works (it wraps and unwraps my private value successfully), but is this implementation secure?
Wrapping
byte[] salt = .... // 32 random bytes...
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
c.init(Cipher.WRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
byte[] result = c.wrap(wk); // wrapped private value
Unwrapping
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and previous salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
IvParameterSpec iv = new IvParameterSpec(parsed.getIv()); // previously created iv
c.init(Cipher.UNWRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
Key result = c.unwrap(parsed.getKey(), "AES", Cipher.SECRET_KEY);
byte[] pv = result.getEncoded(); // unwrapped private value
Upvotes: 2
Views: 1874
Reputation: 93968
It is possible to use a normal mode of operation rather than a specialized padding mode for AES. The padding mode is nicer, but just CBC with PKCS#7 padding should suffice as well.
It would be wise to use an IV and store it with the wrapped key. Private keys are generally not just binary data, but have structure, and you may leak a tiny bit of information if you wrap multiple keys this way. For RSA the randomized modulus comes before the private exponent / CRT parameters so you should be secure with a zero IV as well.
// --- key pair with private key for testing
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(4096);
KeyPair kp = gen.generateKeyPair();
// --- create KEK
final byte[] kek = new byte[16]; // test value
SecretKey sKey = new SecretKeySpec(kek, "AES");
// --- the cipher, not a special wrapping algorithm
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
// --- create IV
// not really necessary because the modulus comes first, but nicer
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// --- init & wrap by normal encryption
c.init(Cipher.WRAP_MODE, sKey, iv);
byte[] result = c.wrap(kp.getPrivate());
AES-SIV would be nicer, but that's not included in the SunJCE provider. You could use AES-GCM which ads integrity but beware that repeating the 12 byte IV (nonce) for that can be catastrophic for that mode.
Upvotes: 3