org.bouncycastle.openpgp.PGPSecretKey Maven / Gradle / Ivy
package org.bouncycastle.openpgp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Provider;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.BCPGObject;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.ContainedPacket;
import org.bouncycastle.bcpg.DSAPublicBCPGKey;
import org.bouncycastle.bcpg.DSASecretBCPGKey;
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
import org.bouncycastle.bcpg.RSASecretBCPGKey;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SecretKeyPacket;
import org.bouncycastle.bcpg.SecretSubkeyPacket;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.UserAttributePacket;
import org.bouncycastle.bcpg.UserIDPacket;
import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
/**
* general class to handle a PGP secret key object.
*/
public class PGPSecretKey
{
final SecretKeyPacket secret;
final PGPPublicKey pub;
PGPSecretKey(
SecretKeyPacket secret,
PGPPublicKey pub)
{
this.secret = secret;
this.pub = pub;
}
PGPSecretKey(
PGPPrivateKey privKey,
PGPPublicKey pubKey,
int encAlgorithm,
char[] passPhrase,
boolean useSHA1,
SecureRandom rand,
Provider provider)
throws PGPException
{
this(privKey, pubKey, encAlgorithm, passPhrase, useSHA1, rand, false, provider);
}
PGPSecretKey(
PGPPrivateKey privKey,
PGPPublicKey pubKey,
int encAlgorithm,
char[] passPhrase,
boolean useSHA1,
SecureRandom rand,
boolean isMasterKey,
Provider provider)
throws PGPException
{
BCPGObject secKey;
this.pub = pubKey;
switch (pubKey.getAlgorithm())
{
case PGPPublicKey.RSA_ENCRYPT:
case PGPPublicKey.RSA_SIGN:
case PGPPublicKey.RSA_GENERAL:
RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey.getKey();
secKey = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
break;
case PGPPublicKey.DSA:
DSAPrivateKey dsK = (DSAPrivateKey)privKey.getKey();
secKey = new DSASecretBCPGKey(dsK.getX());
break;
case PGPPublicKey.ELGAMAL_ENCRYPT:
case PGPPublicKey.ELGAMAL_GENERAL:
ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey.getKey();
secKey = new ElGamalSecretBCPGKey(esK.getX());
break;
default:
throw new PGPException("unknown key class");
}
String cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
Cipher c = null;
if (cName != null)
{
try
{
c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
}
catch (Exception e)
{
throw new PGPException("Exception creating cipher", e);
}
}
try
{
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
BCPGOutputStream pOut = new BCPGOutputStream(bOut);
pOut.writeObject(secKey);
byte[] keyData = bOut.toByteArray();
pOut.write(checksum(useSHA1, keyData, keyData.length));
if (c != null)
{
byte[] iv = new byte[8];
rand.nextBytes(iv);
S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
SecretKey key = PGPUtil.makeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase, provider);
c.init(Cipher.ENCRYPT_MODE, key, rand);
iv = c.getIV();
byte[] encData = c.doFinal(bOut.toByteArray());
int s2kUsage;
if (useSHA1)
{
s2kUsage = SecretKeyPacket.USAGE_SHA1;
}
else
{
s2kUsage = SecretKeyPacket.USAGE_CHECKSUM;
}
if (isMasterKey)
{
this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
}
else
{
this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
}
}
else
{
if (isMasterKey)
{
this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
}
else
{
this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
}
}
}
catch (PGPException e)
{
throw e;
}
catch (Exception e)
{
throw new PGPException("Exception encrypting key", e);
}
}
public PGPSecretKey(
int certificationLevel,
PGPKeyPair keyPair,
String id,
int encAlgorithm,
char[] passPhrase,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
SecureRandom rand,
String provider)
throws PGPException, NoSuchProviderException
{
this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider);
}
public PGPSecretKey(
int certificationLevel,
PGPKeyPair keyPair,
String id,
int encAlgorithm,
char[] passPhrase,
boolean useSHA1,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
SecureRandom rand,
String provider)
throws PGPException, NoSuchProviderException
{
this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider));
}
public PGPSecretKey(
int certificationLevel,
PGPKeyPair keyPair,
String id,
int encAlgorithm,
char[] passPhrase,
boolean useSHA1,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
SecureRandom rand,
Provider provider)
throws PGPException
{
this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, provider), encAlgorithm, passPhrase, useSHA1, rand, true, provider);
}
private static PGPPublicKey certifiedPublicKey(
int certificationLevel,
PGPKeyPair keyPair,
String id,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
Provider provider)
throws PGPException
{
PGPSignatureGenerator sGen;
try
{
sGen = new PGPSignatureGenerator(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1, provider);
}
catch (Exception e)
{
throw new PGPException("creating signature generator: " + e, e);
}
//
// generate the certification
//
sGen.initSign(certificationLevel, keyPair.getPrivateKey());
sGen.setHashedSubpackets(hashedPcks);
sGen.setUnhashedSubpackets(unhashedPcks);
try
{
PGPSignature certification = sGen.generateCertification(id, keyPair.getPublicKey());
return PGPPublicKey.addCertification(keyPair.getPublicKey(), id, certification);
}
catch (Exception e)
{
throw new PGPException("exception doing certification: " + e, e);
}
}
public PGPSecretKey(
int certificationLevel,
int algorithm,
PublicKey pubKey,
PrivateKey privKey,
Date time,
String id,
int encAlgorithm,
char[] passPhrase,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
SecureRandom rand,
String provider)
throws PGPException, NoSuchProviderException
{
this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, hashedPcks, unhashedPcks, rand, provider);
}
public PGPSecretKey(
int certificationLevel,
int algorithm,
PublicKey pubKey,
PrivateKey privKey,
Date time,
String id,
int encAlgorithm,
char[] passPhrase,
boolean useSHA1,
PGPSignatureSubpacketVector hashedPcks,
PGPSignatureSubpacketVector unhashedPcks,
SecureRandom rand,
String provider)
throws PGPException, NoSuchProviderException
{
this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider);
}
/**
* Return true if this key has an algorithm type that makes it suitable to use for signing.
*
* Note: with version 4 keys KeyFlags subpackets should also be considered when present for
* determining the preferred use of the key.
*
* @return true if this key algorithm is suitable for use with signing.
*/
public boolean isSigningKey()
{
int algorithm = pub.getAlgorithm();
return ((algorithm == PGPPublicKey.RSA_GENERAL) || (algorithm == PGPPublicKey.RSA_SIGN)
|| (algorithm == PGPPublicKey.DSA) || (algorithm == PGPPublicKey.ECDSA) || (algorithm == PGPPublicKey.ELGAMAL_GENERAL));
}
/**
* Return true if this is a master key.
* @return true if a master key.
*/
public boolean isMasterKey()
{
return pub.isMasterKey();
}
/**
* return the algorithm the key is encrypted with.
*
* @return the algorithm used to encrypt the secret key.
*/
public int getKeyEncryptionAlgorithm()
{
return secret.getEncAlgorithm();
}
/**
* Return the keyID of the public key associated with this key.
*
* @return the keyID associated with this key.
*/
public long getKeyID()
{
return pub.getKeyID();
}
/**
* Return the public key associated with this key.
*
* @return the public key for this key.
*/
public PGPPublicKey getPublicKey()
{
return pub;
}
/**
* Return any userIDs associated with the key.
*
* @return an iterator of Strings.
*/
public Iterator getUserIDs()
{
return pub.getUserIDs();
}
/**
* Return any user attribute vectors associated with the key.
*
* @return an iterator of Strings.
*/
public Iterator getUserAttributes()
{
return pub.getUserAttributes();
}
private byte[] extractKeyData(
char[] passPhrase,
Provider provider)
throws PGPException
{
String cName = PGPUtil.getSymmetricCipherName(secret.getEncAlgorithm());
Cipher c = null;
if (cName != null)
{
try
{
c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
}
catch (Exception e)
{
throw new PGPException("Exception creating cipher", e);
}
}
byte[] encData = secret.getSecretKeyData();
byte[] data = null;
try
{
if (c != null)
{
try
{
if (secret.getPublicKeyPacket().getVersion() == 4)
{
IvParameterSpec ivSpec = new IvParameterSpec(secret.getIV());
SecretKey key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
c.init(Cipher.DECRYPT_MODE, key, ivSpec);
data = c.doFinal(encData, 0, encData.length);
boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
byte[] check = checksum(useSHA1, data, (useSHA1) ? data.length - 20 : data.length - 2);
for (int i = 0; i != check.length; i++)
{
if (check[i] != data[data.length - check.length + i])
{
throw new PGPException("checksum mismatch at " + i + " of " + check.length);
}
}
}
else // version 2 or 3, RSA only.
{
SecretKey key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
data = new byte[encData.length];
byte[] iv = new byte[secret.getIV().length];
System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
//
// read in the four numbers
//
int pos = 0;
for (int i = 0; i != 4; i++)
{
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
data[pos] = encData[pos];
data[pos + 1] = encData[pos + 1];
c.doFinal(encData, pos + 2, encLen, data, pos + 2);
pos += 2 + encLen;
if (i != 3)
{
System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
}
}
//
// verify checksum
//
int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
int calcCs = 0;
for (int j=0; j < data.length-2; j++)
{
calcCs += data[j] & 0xff;
}
calcCs &= 0xffff;
if (calcCs != cs)
{
throw new PGPException("checksum mismatch: passphrase wrong, expected "
+ Integer.toHexString(cs)
+ " found " + Integer.toHexString(calcCs));
}
}
}
catch (PGPException e)
{
throw e;
}
catch (Exception e)
{
throw new PGPException("Exception decrypting key", e);
}
}
else
{
data = encData;
}
return data;
}
catch (PGPException e)
{
throw e;
}
catch (Exception e)
{
throw new PGPException("Exception constructing key", e);
}
}
/**
* Extract a PGPPrivate key from the SecretKey's encrypted contents.
*
* @param passPhrase
* @param provider
* @return PGPPrivateKey
* @throws PGPException
* @throws NoSuchProviderException
*/
public PGPPrivateKey extractPrivateKey(
char[] passPhrase,
String provider)
throws PGPException, NoSuchProviderException
{
return extractPrivateKey(passPhrase, PGPUtil.getProvider(provider));
}
/**
* Extract a PGPPrivate key from the SecretKey's encrypted contents.
*
* @param passPhrase
* @param provider
* @return PGPPrivateKey
* @throws PGPException
*/
public PGPPrivateKey extractPrivateKey(
char[] passPhrase,
Provider provider)
throws PGPException
{
byte[] secKeyData = secret.getSecretKeyData();
if (secKeyData == null || secKeyData.length < 1)
{
return null;
}
PublicKeyPacket pubPk = secret.getPublicKeyPacket();
try
{
KeyFactory fact;
byte[] data = extractKeyData(passPhrase, provider);
BCPGInputStream in = new BCPGInputStream(new ByteArrayInputStream(data));
switch (pubPk.getAlgorithm())
{
case PGPPublicKey.RSA_ENCRYPT:
case PGPPublicKey.RSA_GENERAL:
case PGPPublicKey.RSA_SIGN:
RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
RSASecretBCPGKey rsaPriv = new RSASecretBCPGKey(in);
RSAPrivateCrtKeySpec rsaPrivSpec = new RSAPrivateCrtKeySpec(
rsaPriv.getModulus(),
rsaPub.getPublicExponent(),
rsaPriv.getPrivateExponent(),
rsaPriv.getPrimeP(),
rsaPriv.getPrimeQ(),
rsaPriv.getPrimeExponentP(),
rsaPriv.getPrimeExponentQ(),
rsaPriv.getCrtCoefficient());
fact = KeyFactory.getInstance("RSA", provider);
return new PGPPrivateKey(fact.generatePrivate(rsaPrivSpec), this.getKeyID());
case PGPPublicKey.DSA:
DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
DSASecretBCPGKey dsaPriv = new DSASecretBCPGKey(in);
DSAPrivateKeySpec dsaPrivSpec =
new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG());
fact = KeyFactory.getInstance("DSA", provider);
return new PGPPrivateKey(fact.generatePrivate(dsaPrivSpec), this.getKeyID());
case PGPPublicKey.ELGAMAL_ENCRYPT:
case PGPPublicKey.ELGAMAL_GENERAL:
ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
ElGamalSecretBCPGKey elPriv = new ElGamalSecretBCPGKey(in);
ElGamalPrivateKeySpec elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG()));
fact = KeyFactory.getInstance("ElGamal", provider);
return new PGPPrivateKey(fact.generatePrivate(elSpec), this.getKeyID());
default:
throw new PGPException("unknown public key algorithm encountered");
}
}
catch (PGPException e)
{
throw e;
}
catch (Exception e)
{
throw new PGPException("Exception constructing key", e);
}
}
private static byte[] checksum(boolean useSHA1, byte[] bytes, int length)
throws PGPException
{
if (useSHA1)
{
try
{
MessageDigest dig = MessageDigest.getInstance("SHA1");
dig.update(bytes, 0, length);
return dig.digest();
}
catch (NoSuchAlgorithmException e)
{
throw new PGPException("Can't find SHA-1", e);
}
}
else
{
int checksum = 0;
for (int i = 0; i != length; i++)
{
checksum += bytes[i] & 0xff;
}
byte[] check = new byte[2];
check[0] = (byte)(checksum >> 8);
check[1] = (byte)checksum;
return check;
}
}
public byte[] getEncoded()
throws IOException
{
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
this.encode(bOut);
return bOut.toByteArray();
}
public void encode(
OutputStream outStream)
throws IOException
{
BCPGOutputStream out;
if (outStream instanceof BCPGOutputStream)
{
out = (BCPGOutputStream)outStream;
}
else
{
out = new BCPGOutputStream(outStream);
}
out.writePacket(secret);
if (pub.trustPk != null)
{
out.writePacket(pub.trustPk);
}
if (pub.subSigs == null) // is not a sub key
{
for (int i = 0; i != pub.keySigs.size(); i++)
{
((PGPSignature)pub.keySigs.get(i)).encode(out);
}
for (int i = 0; i != pub.ids.size(); i++)
{
if (pub.ids.get(i) instanceof String)
{
String id = (String)pub.ids.get(i);
out.writePacket(new UserIDPacket(id));
}
else
{
PGPUserAttributeSubpacketVector v = (PGPUserAttributeSubpacketVector)pub.ids.get(i);
out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
}
if (pub.idTrusts.get(i) != null)
{
out.writePacket((ContainedPacket)pub.idTrusts.get(i));
}
List sigs = (ArrayList)pub.idSigs.get(i);
for (int j = 0; j != sigs.size(); j++)
{
((PGPSignature)sigs.get(j)).encode(out);
}
}
}
else
{
for (int j = 0; j != pub.subSigs.size(); j++)
{
((PGPSignature)pub.subSigs.get(j)).encode(out);
}
}
}
/**
* Return a copy of the passed in secret key, encrypted using a new
* password and the passed in algorithm.
*
* @param key the PGPSecretKey to be copied.
* @param oldPassPhrase the current password for key.
* @param newPassPhrase the new password for the key.
* @param newEncAlgorithm the algorithm to be used for the encryption.
* @param rand source of randomness.
* @param provider name of the provider to use
*/
public static PGPSecretKey copyWithNewPassword(
PGPSecretKey key,
char[] oldPassPhrase,
char[] newPassPhrase,
int newEncAlgorithm,
SecureRandom rand,
String provider)
throws PGPException, NoSuchProviderException
{
return copyWithNewPassword(key, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, PGPUtil.getProvider(provider));
}
/**
* Return a copy of the passed in secret key, encrypted using a new
* password and the passed in algorithm.
*
* @param key the PGPSecretKey to be copied.
* @param oldPassPhrase the current password for key.
* @param newPassPhrase the new password for the key.
* @param newEncAlgorithm the algorithm to be used for the encryption.
* @param rand source of randomness.
* @param provider the provider to use
*/
public static PGPSecretKey copyWithNewPassword(
PGPSecretKey key,
char[] oldPassPhrase,
char[] newPassPhrase,
int newEncAlgorithm,
SecureRandom rand,
Provider provider)
throws PGPException
{
byte[] rawKeyData = key.extractKeyData(oldPassPhrase, provider);
int s2kUsage = key.secret.getS2KUsage();
byte[] iv = null;
S2K s2k = null;
byte[] keyData;
if (newEncAlgorithm == SymmetricKeyAlgorithmTags.NULL)
{
s2kUsage = SecretKeyPacket.USAGE_NONE;
if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1) // SHA-1 hash, need to rewrite checksum
{
keyData = new byte[rawKeyData.length - 18];
System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);
byte[] check = checksum(false, keyData, keyData.length - 2);
keyData[keyData.length - 2] = check[0];
keyData[keyData.length - 1] = check[1];
}
else
{
keyData = rawKeyData;
}
}
else
{
Cipher c = null;
String cName = PGPUtil.getSymmetricCipherName(newEncAlgorithm);
try
{
c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
}
catch (Exception e)
{
throw new PGPException("Exception creating cipher", e);
}
iv = new byte[8];
rand.nextBytes(iv);
s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
try
{
SecretKey sKey = PGPUtil.makeKeyFromPassPhrase(newEncAlgorithm, s2k, newPassPhrase, provider);
c.init(Cipher.ENCRYPT_MODE, sKey, rand);
iv = c.getIV();
keyData = c.doFinal(rawKeyData);
}
catch (PGPException e)
{
throw e;
}
catch (Exception e)
{
throw new PGPException("Exception encrypting key", e);
}
}
SecretKeyPacket secret = null;
if (key.secret instanceof SecretSubkeyPacket)
{
secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
}
else
{
secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
newEncAlgorithm, s2kUsage, s2k, iv, keyData);
}
return new PGPSecretKey(secret, key.pub);
}
/**
* Replace the passed the public key on the passed in secret key.
*
* @param secretKey secret key to change
* @param publicKey new public key.
* @return a new secret key.
* @throws IllegalArgumentException if keyIDs do not match.
*/
public static PGPSecretKey replacePublicKey(PGPSecretKey secretKey, PGPPublicKey publicKey)
{
if (publicKey.getKeyID() != secretKey.getKeyID())
{
throw new IllegalArgumentException("keyIDs do not match");
}
return new PGPSecretKey(secretKey.secret, publicKey);
}
}