Skip to content

Commit

Permalink
Working SecretKey locking and unlocking using AEAD and Argon2
Browse files Browse the repository at this point in the history
  • Loading branch information
vanitasvitae committed Jun 23, 2023
1 parent 4b661f7 commit f6deb85
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 63 deletions.
13 changes: 7 additions & 6 deletions pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ public SecretKeyPacket(
this.iv = iv;
this.secKeyData = secKeyData;

if (s2k != null && s2k.getType() == S2K.ARGON_2 && s2kUsage != USAGE_AEAD) {
throw new IllegalArgumentException("Argon2 is only used with AEAD (S2K usage octet 253)");
}

if (pubKeyPacket.getVersion() == VERSION_6) {
if (s2kUsage == USAGE_CHECKSUM) {
throw new IllegalArgumentException("Version 6 keys MUST NOT use S2K usage USAGE_CHECKSUM");
Expand Down Expand Up @@ -190,7 +194,7 @@ public static SecretKeyPacket createV4SecretKey(PublicKeyPacket pubKeyPacket,

/**
* Create a v6 secret key packet.
* For AEAD encryption use {@link #createAeadEncryptedV6SecretKey(PublicKeyPacket, int, int, byte[], S2K, byte[])} instead.
* For AEAD encryption use {@link #createAeadEncryptedSecretKey(PublicKeyPacket, int, int, byte[], S2K, byte[])} instead.
*
* @param pubKeyPacket public key packet
* @param encAlgorithm encryption algorithm
Expand Down Expand Up @@ -220,7 +224,7 @@ public static SecretKeyPacket createV6SecretKey(
}

/**
* Create an AEAD encrypted v6 secret key packet.
* Create an AEAD encrypted secret key packet.
*
* @param pubKeyPacket public key packet
* @param encAlgorithm encryption algorithm
Expand All @@ -230,17 +234,14 @@ public static SecretKeyPacket createV6SecretKey(
* @param secKeyData encrypted secret key data with appended AEAD auth tag
* @return secret key packet
*/
public static SecretKeyPacket createAeadEncryptedV6SecretKey(
public static SecretKeyPacket createAeadEncryptedSecretKey(
PublicKeyPacket pubKeyPacket,
int encAlgorithm,
int aeadAlgorithm,
byte[] aeadNonce,
S2K s2k,
byte[] secKeyData)
{
if (pubKeyPacket.getVersion() != VERSION_6) {
throw new IllegalArgumentException("Pubkey version mismatch. Expected 6, got " + pubKeyPacket.getVersion());
}
return new SecretKeyPacket(pubKeyPacket, encAlgorithm, aeadAlgorithm, USAGE_AEAD, s2k, aeadNonce, secKeyData);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public static SecretSubkeyPacket createV4SecretSubkey(

/**
* Create a v6 secret subkey packet.
* For AEAD encryption use {@link #createAeadEncryptedV6SecretSubkey(PublicSubkeyPacket, int, int, byte[], S2K, byte[])} instead.
* For AEAD encryption use {@link #createAeadEncryptedSecretSubkey(PublicSubkeyPacket, int, int, byte[], S2K, byte[])} instead.
*
* @param pubKeyPacket public subkey packet
* @param encAlgorithm encryption algorithm
Expand Down Expand Up @@ -142,7 +142,7 @@ public static SecretKeyPacket createV6SecretSubkey(
}

/**
* Create an AEAD encrypted v6 secret subkey packet.
* Create an AEAD encrypted secret subkey packet.
* @param pubKeyPacket public subkey packet
* @param encAlgorithm encryption algorithm
* @param aeadAlgorithm aead algorithm
Expand All @@ -151,17 +151,14 @@ public static SecretKeyPacket createV6SecretSubkey(
* @param secKeyData encrypted secret key data with appended AEAD auth tag
* @return secret key packet
*/
public static SecretKeyPacket createAeadEncryptedV6SecretSubkey(
public static SecretKeyPacket createAeadEncryptedSecretSubkey(
PublicSubkeyPacket pubKeyPacket,
int encAlgorithm,
int aeadAlgorithm,
byte[] aeadNonce,
S2K s2k,
byte[] secKeyData)
{
if (pubKeyPacket.getVersion() != VERSION_6) {
throw new IllegalArgumentException("Pubkey version mismatch. Expected 6, got " + pubKeyPacket.getVersion());
}
return new SecretSubkeyPacket(pubKeyPacket, encAlgorithm, aeadAlgorithm, USAGE_AEAD, s2k, aeadNonce, secKeyData);
}

Expand Down
77 changes: 68 additions & 9 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.bouncycastle.bcpg.AEADUtils;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.BCPGObject;
import org.bouncycastle.bcpg.BCPGOutputStream;
Expand All @@ -19,6 +21,7 @@
import org.bouncycastle.bcpg.EdSecretBCPGKey;
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PacketTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.PublicSubkeyPacket;
Expand Down Expand Up @@ -856,17 +859,22 @@ public static PGPSecretKey copyWithNewPassword(
byte[] keyData;
int newEncAlgorithm = SymmetricKeyAlgorithmTags.NULL;

// key will be unencrypted
if (newKeyEncryptor == null || newKeyEncryptor.getAlgorithm() == SymmetricKeyAlgorithmTags.NULL)
{
s2kUsage = SecretKeyPacket.USAGE_NONE;
if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1) // SHA-1 hash, need to rewrite checksum
{
// strip SHA1 checksum
keyData = new byte[rawKeyData.length - 18];

// Copy raw key bytes
System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);

// Calculate 2-byte checksum
byte[] check = checksum(null, keyData, keyData.length - 2);

// append 2 byte checksum
keyData[keyData.length - 2] = check[0];
keyData[keyData.length - 1] = check[1];
}
Expand All @@ -875,8 +883,9 @@ public static PGPSecretKey copyWithNewPassword(
keyData = rawKeyData;
}
}
else
else // key will be encrypted
{
// OpenPGP v3
if (key.secret.getPublicKeyPacket().getVersion() < 4)
{
if (s2kUsage == SecretKeyPacket.USAGE_NONE)
Expand Down Expand Up @@ -939,12 +948,33 @@ public static PGPSecretKey copyWithNewPassword(
}
else
{
// key WAS unencrypted, but will be encrypted
if (s2kUsage == SecretKeyPacket.USAGE_NONE)
{
if (checksumCalculator != null)
if (newKeyEncryptor.getS2K() != null && newKeyEncryptor.getS2K().getType() == S2K.ARGON_2)
{
if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1)
{
byte[] encKey = newKeyEncryptor.getKey();
newEncAlgorithm = newKeyEncryptor.getAlgorithm();
int aeadAlgorithm = newKeyEncryptor.getAEADAlgorithm();
// HKDF
// [tag, version, symAlg, aeadAlg]
byte[] hkdfInfo = new byte[] {
(byte) (key.secret instanceof SecretSubkeyPacket ?
0xC0 | PacketTags.SECRET_SUBKEY :
0xC0 | PacketTags.SECRET_KEY), // TODO: 0xC0 | secret.getPacketTag()
(byte) key.secret.getVersion(),
(byte) newEncAlgorithm,
(byte) aeadAlgorithm
};
// AEAD
byte[] aad = key.getAADFromPublicKey(key);
// AEAD Nonce (IV)
byte[] aeadIv = newKeyEncryptor.getCipherIV();
keyData = newKeyEncryptor.encryptKeyData(encKey, rawKeyData, hkdfInfo, aad, aeadIv);
}
else if (checksumCalculator != null) // USAGE_SHA1
{
if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1) {
throw new IllegalArgumentException("only SHA-1 supported for checksums");
}
s2kUsage = SecretKeyPacket.USAGE_SHA1;
Expand All @@ -953,14 +983,15 @@ public static PGPSecretKey copyWithNewPassword(
rawKeyData = Arrays.concatenate(rawKeyData, check);
keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
}
else
else // USAGE_CHECKSUM (deprecated!)
{
s2kUsage = SecretKeyPacket.USAGE_CHECKSUM;
keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
}
}
else
{
// key was and will be encrypted
keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
}

Expand All @@ -975,18 +1006,46 @@ public static PGPSecretKey copyWithNewPassword(
SecretKeyPacket secret;
if (key.secret instanceof SecretSubkeyPacket)
{
secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
if (newKeyEncryptor != null && newKeyEncryptor.getAEADAlgorithm() != 0)
{
secret = SecretSubkeyPacket.createAeadEncryptedSecretSubkey(
(PublicSubkeyPacket) key.secret.getPublicKeyPacket(),
newEncAlgorithm, newKeyEncryptor.getAEADAlgorithm(), iv, s2k, keyData);
}
else
{
secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
}
}
else
{
secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
if (newKeyEncryptor != null && newKeyEncryptor.getAEADAlgorithm() != 0) {
secret = SecretKeyPacket.createAeadEncryptedSecretKey(
key.secret.getPublicKeyPacket(), newEncAlgorithm, newKeyEncryptor.getAEADAlgorithm(),
iv, s2k, keyData);
}
else
{
secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
}
}

return new PGPSecretKey(secret, key.pub);
}

private byte[] getAADFromPublicKey(PGPSecretKey key) throws PGPException {
try {
return Arrays.prepend(key.getPublicKey().publicPk.getEncodedContents(),
(byte) (key.secret instanceof SecretSubkeyPacket ?
0xC0 | PacketTags.SECRET_SUBKEY :
0xC0 | PacketTags.SECRET_KEY));
} catch (IOException e) {
throw new PGPException("Cannot encode public key contents.");
}
}

/**
* Replace the passed the public key on the passed in secret key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
public abstract class PBESecretKeyEncryptor
{
protected int encAlgorithm;
protected int aeadAlgorithm;
protected char[] passPhrase;
protected PGPDigestCalculator s2kDigestCalculator;
protected int s2kCount;
Expand Down Expand Up @@ -35,6 +36,14 @@ protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestC
this.s2kCount = s2kCount;
}

protected PBESecretKeyEncryptor(int encAlgorithm, int aeadAlgorithm, S2K.Argon2Params argon2Params, char[] passPhrase)
{
this.encAlgorithm = encAlgorithm;
this.aeadAlgorithm = aeadAlgorithm;
this.s2k = new S2K(argon2Params);
this.passPhrase = passPhrase;
}

public int getAlgorithm()
{
return encAlgorithm;
Expand Down Expand Up @@ -100,5 +109,11 @@ public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff,
throw new PGPException("encryption of version 3 keys not supported.");
}

public abstract byte[] encryptKeyData(byte[] kek, byte[] secretKeyData, byte[] hkdfInfo, byte[] aad, byte[] iv) throws PGPException;

public abstract byte[] getCipherIV();

public int getAEADAlgorithm() {
return aeadAlgorithm;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ public byte[] decryptAEAD(int encAlgoritm, int aeadAlgorithm, byte[] key, int ae
return decrypted;
}

public byte[] encryptAEAD(int encAlgorithm, int aeadAlgorithm, byte[] kek, int aeadMacLen, byte[] iv, byte[] secretKeyData, byte[] aad)
throws PGPException, InvalidCipherTextException {
final KeyParameter aeadSecretKey = new KeyParameter(kek);

AEADBlockCipher aead = BcAEADUtil.createAEADCipher(encAlgorithm, aeadAlgorithm);
AEADParameters parameters = new AEADParameters(aeadSecretKey, aeadMacLen, iv, aad);
aead.init(true, parameters);

byte[] ciphertextAndAuthTag = new byte[aead.getOutputSize(secretKeyData.length)];
int dataLen = aead.processBytes(secretKeyData, 0, secretKeyData.length, ciphertextAndAuthTag, 0);
aead.doFinal(ciphertextAndAuthTag, dataLen);
return ciphertextAndAuthTag;
}

/**
* Generate a nonce by xor-ing the given iv with the chunk index.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] key
}

@Override
public byte[] recoverAEADEncryptedKeyData(SecretKeyPacket secret, byte[] key) throws IOException, PGPException {
public byte[] recoverAEADEncryptedKeyData(SecretKeyPacket secret, byte[] key)
throws IOException, PGPException
{
// HKDF
// [tag, version, symAlg, aeadAlg]
byte[] hkdfInfo = new byte[] {
Expand Down Expand Up @@ -81,7 +83,7 @@ public byte[] recoverAEADEncryptedKeyData(SecretKeyPacket secret, byte[] key) th
}
catch (PGPException | InvalidCipherTextException e)
{
throw new PGPException("Exception recovering session info", e);
throw new PGPException("Exception recovering secret key data", e);
}

return sessionData;
Expand Down
Loading

0 comments on commit f6deb85

Please sign in to comment.