All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.openpgp.PGPUtil Maven / Gradle / Ivy

Go to download

The Bouncy Castle Java APIs for the OpenPGP Protocol. The APIs are designed primarily to be used in conjunction with the BC FIPS provider. The APIs may also be used with other providers although if being used in a FIPS context it is the responsibility of the user to ensure that any other providers used are FIPS certified and used appropriately.

There is a newer version: 2.0.9
Show newest version
package org.bouncycastle.openpgp;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.MPInteger;
import org.bouncycastle.bcpg.PacketTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.DecoderException;

/**
 * PGP utilities.
 */
public class PGPUtil
    implements HashAlgorithmTags
{
    private static String defProvider = "BC";

    private static Map nameToHashId = new HashMap()
    {
        {
            put("sha1", Integers.valueOf(HashAlgorithmTags.SHA1));
            put("sha224", Integers.valueOf(HashAlgorithmTags.SHA224));
            put("sha256", Integers.valueOf(HashAlgorithmTags.SHA256));
            put("sha384", Integers.valueOf(HashAlgorithmTags.SHA384));
            put("sha512", Integers.valueOf(HashAlgorithmTags.SHA512));
            put("sha3-224", Integers.valueOf(HashAlgorithmTags.SHA3_224));
            put("sha3-256", Integers.valueOf(HashAlgorithmTags.SHA3_256));
            put("sha3-384", Integers.valueOf(HashAlgorithmTags.SHA3_384));
            put("sha3-512", Integers.valueOf(HashAlgorithmTags.SHA3_512));
            put("ripemd160", Integers.valueOf(HashAlgorithmTags.RIPEMD160));
            put("rmd160", Integers.valueOf(HashAlgorithmTags.RIPEMD160));
            put("md2", Integers.valueOf(HashAlgorithmTags.MD2));
            put("md4", Integers.valueOf(HashAlgorithmTags.MD4));
            put("tiger", Integers.valueOf(HashAlgorithmTags.TIGER_192));
            put("haval", Integers.valueOf(HashAlgorithmTags.HAVAL_5_160));
            put("sm3", Integers.valueOf(HashAlgorithmTags.SM3));
            put("md5", Integers.valueOf(HashAlgorithmTags.MD5));
        }
    };

    private static Map oidToName = new HashMap()
    {
        {
            put(CryptlibObjectIdentifiers.curvey25519, "Curve25519");
            put(GNUObjectIdentifiers.Ed25519, "Ed25519Legacy");
            put(EdECObjectIdentifiers.id_X25519, "Curve25519");
            put(EdECObjectIdentifiers.id_Ed25519, "Ed25519");
            put(SECObjectIdentifiers.secp256r1, "NIST P-256");
            put(SECObjectIdentifiers.secp384r1, "NIST P-384");
            put(SECObjectIdentifiers.secp521r1, "NIST P-521");
            put(TeleTrusTObjectIdentifiers.brainpoolP256r1, "brainpoolP256r1");
            put(TeleTrusTObjectIdentifiers.brainpoolP384r1, "brainpoolP384r1");
            put(TeleTrusTObjectIdentifiers.brainpoolP512r1, "brainpoolP512r1");
        }
    };

    /**
     * Return an appropriate name for the hash algorithm represented by the passed
     * in hash algorithm ID number.
     *
     * @param hashAlgorithm the algorithm ID for a hash algorithm.
     * @return a String representation of the hash name.
     */
    public static String getDigestName(
        int hashAlgorithm)
        throws PGPException
    {
        switch (hashAlgorithm)
        {
        case HashAlgorithmTags.SHA1:
            return "SHA1";
        case HashAlgorithmTags.MD2:
            return "MD2";
        case HashAlgorithmTags.MD5:
            return "MD5";
        case HashAlgorithmTags.RIPEMD160:
            return "RIPEMD160";
        case HashAlgorithmTags.SHA256:
            return "SHA256";
        case HashAlgorithmTags.SHA384:
            return "SHA384";
        case HashAlgorithmTags.SHA512:
            return "SHA512";
        case HashAlgorithmTags.SHA224:
            return "SHA224";
        case HashAlgorithmTags.SHA3_256:
        case HashAlgorithmTags.SHA3_256_OLD:
            return "SHA3-256";
        case HashAlgorithmTags.SHA3_384:
            return "SHA3-384";
        case HashAlgorithmTags.SHA3_512:
        case HashAlgorithmTags.SHA3_512_OLD:
            return "SHA3-512";
        case HashAlgorithmTags.SHA3_224:
            return "SHA3-224";
        case HashAlgorithmTags.TIGER_192:
            return "TIGER";
        default:
            throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
        }
    }


    public static int getDigestIDForName(String name)
    {
        name = Strings.toLowerCase(name);
        if (nameToHashId.containsKey(name))
        {
            return ((Integer)nameToHashId.get(name)).intValue();
        }
        throw new IllegalArgumentException("unable to map " + name + " to a hash id");
    }

    /**
     * Return the EC curve name for the passed in OID.
     *
     * @param oid the EC curve object identifier in the PGP key
     * @return a string representation of the OID.
     */
    public static String getCurveName(
        ASN1ObjectIdentifier oid)
    {
        String name = (String)oidToName.get(oid);

        if (name != null)
        {
            return name;
        }

        // fall back
        return ECNamedCurveTable.getName(oid);
    }

    /**
     * Return an appropriate name for the signature algorithm represented by the passed
     * in public key and hash algorithm ID numbers.
     *
     * @param keyAlgorithm  the algorithm ID for the public key algorithm used in the signature.
     * @param hashAlgorithm the algorithm ID for the hash algorithm used.
     * @return a String representation of the signature name.
     */
    public static String getSignatureName(
        int keyAlgorithm,
        int hashAlgorithm)
        throws PGPException
    {
        String encAlg;

        switch (keyAlgorithm)
        {
        case PublicKeyAlgorithmTags.RSA_GENERAL:
        case PublicKeyAlgorithmTags.RSA_SIGN:
            encAlg = "RSA";
            break;
        case PublicKeyAlgorithmTags.DSA:
            encAlg = "DSA";
            break;
        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
            encAlg = "ElGamal";
            break;
        default:
            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
        }

        return getDigestName(hashAlgorithm) + "with" + encAlg;
    }

    /**
     * Return an appropriate name for the symmetric algorithm represented by the passed
     * in symmetric algorithm ID number.
     *
     * @param algorithm the algorithm ID for a symmetric cipher.
     * @return a String representation of the cipher name.
     */
    public static String getSymmetricCipherName(
        int algorithm)
    {
        switch (algorithm)
        {
        case SymmetricKeyAlgorithmTags.NULL:
            return null;
        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
            return "DESEDE";
        case SymmetricKeyAlgorithmTags.IDEA:
            return "IDEA";
        case SymmetricKeyAlgorithmTags.CAST5:
            return "CAST5";
        case SymmetricKeyAlgorithmTags.BLOWFISH:
            return "Blowfish";
        case SymmetricKeyAlgorithmTags.SAFER:
            return "SAFER";
        case SymmetricKeyAlgorithmTags.DES:
            return "DES";
        case SymmetricKeyAlgorithmTags.AES_128:
        case SymmetricKeyAlgorithmTags.AES_192:
        case SymmetricKeyAlgorithmTags.AES_256:
            return "AES";
        case SymmetricKeyAlgorithmTags.CAMELLIA_128:
        case SymmetricKeyAlgorithmTags.CAMELLIA_192:
        case SymmetricKeyAlgorithmTags.CAMELLIA_256:
            return "Camellia";
        case SymmetricKeyAlgorithmTags.TWOFISH:
            return "Twofish";
        default:
            throw new IllegalArgumentException("unknown symmetric algorithm: " + algorithm);
        }
    }


    /**
     * Return the JCA/JCE provider that will be used by factory classes in situations where a
     * provider must be determined on the fly.
     *
     * @return the name of the default provider.
     * @deprecated unused
     */
    public static String getDefaultProvider()
    {
        // TODO: no longer used.
        return defProvider;
    }

    /**
     * Set the provider to be used by the package when it is necessary to find one on the fly.
     *
     * @param provider the name of the JCA/JCE provider to use by default.
     * @deprecated unused
     */
    public static void setDefaultProvider(
        String provider)
    {
        defProvider = provider;
    }

    static MPInteger[] dsaSigToMpi(
        byte[] encoding)
        throws PGPException
    {
        ASN1Integer i1, i2;

        try
        {
            ASN1Sequence s = ASN1Sequence.getInstance(encoding);

            i1 = ASN1Integer.getInstance(s.getObjectAt(0));
            i2 = ASN1Integer.getInstance(s.getObjectAt(1));
        }
        catch (RuntimeException e)
        {
            throw new PGPException("exception decoding signature", e);
        }

        return new MPInteger[]{
            new MPInteger(i1.getValue()),
            new MPInteger(i2.getValue())
        };
    }

    /**
     * Return true if the byte[] blob probably represents key ring data.
     *
     * @return true if data likely represents a key ring stream.
     */
    public static boolean isKeyRing(byte[] blob)
        throws IOException
    {
        BCPGInputStream bIn = new BCPGInputStream(new ByteArrayInputStream(blob));

        int tag = bIn.nextPacketTag();

        return tag == PacketTags.PUBLIC_KEY || tag == PacketTags.PUBLIC_SUBKEY
            || tag == PacketTags.SECRET_KEY || tag == PacketTags.SECRET_SUBKEY;
    }

    /**
     * Return true if the byte[] blob probably represents key box data.
     *
     * @return true if data likely represents a key box stream.
     */
    public static boolean isKeyBox(byte[] data)
        throws IOException
    {
        if (data.length < 12)
        {
            return false;
        }

        InputStream bIn = new ByteArrayInputStream(data);

        // skip size and headers
        for (int i = 0; i != 8; i++)
        {
            bIn.read();
        }

        return bIn.read() == 'K' && bIn.read() == 'B' && bIn.read() == 'X' && bIn.read() == 'f';
    }

    /**
     * Generates a random key for a {@link SymmetricKeyAlgorithmTags symmetric encryption algorithm}
     * .
     *
     * @param algorithm the symmetric key algorithm identifier.
     * @param random    a source of random data.
     * @return a key of the length required by the specified encryption algorithm.
     * @throws PGPException if the encryption algorithm is unknown.
     */
    public static byte[] makeRandomKey(
        int algorithm,
        SecureRandom random)
        throws PGPException
    {
        int keySize;

        switch (algorithm)
        {
        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
        case SymmetricKeyAlgorithmTags.AES_192:
        case SymmetricKeyAlgorithmTags.CAMELLIA_192:
            keySize = 192;
            break;
        case SymmetricKeyAlgorithmTags.IDEA:
        case SymmetricKeyAlgorithmTags.CAST5:
        case SymmetricKeyAlgorithmTags.BLOWFISH:
        case SymmetricKeyAlgorithmTags.SAFER:
        case SymmetricKeyAlgorithmTags.AES_128:
        case SymmetricKeyAlgorithmTags.CAMELLIA_128:
            keySize = 128;
            break;
        case SymmetricKeyAlgorithmTags.DES:
            keySize = 64;
            break;
        case SymmetricKeyAlgorithmTags.AES_256:
        case SymmetricKeyAlgorithmTags.CAMELLIA_256:
        case SymmetricKeyAlgorithmTags.TWOFISH:
            keySize = 256;
            break;
        default:
            throw new PGPException("unknown symmetric algorithm: " + algorithm);
        }

        byte[] keyBytes = new byte[(keySize + 7) / 8];

        random.nextBytes(keyBytes);

        return keyBytes;
    }

    /**
     * Write out the contents of the provided file as a literal data packet.
     *
     * @param out      the stream to write the literal data to.
     * @param fileType the {@link PGPLiteralData} type to use for the file data.
     * @param file     the file to write the contents of.
     * @throws IOException if an error occurs reading the file or writing to the output stream.
     */
    public static void writeFileToLiteralData(
        OutputStream out,
        char fileType,
        File file)
        throws IOException
    {
        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream pOut = lData.open(out, fileType, file);
        pipeFileContents(file, pOut, 32768);
    }

    /**
     * Write out the contents of the provided file as a literal data packet in partial packet
     * format.
     *
     * @param out      the stream to write the literal data to.
     * @param fileType the {@link PGPLiteralData} type to use for the file data.
     * @param file     the file to write the contents of.
     * @param buffer   buffer to be used to chunk the file into partial packets.
     * @throws IOException if an error occurs reading the file or writing to the output stream.
     * @see PGPLiteralDataGenerator#open(OutputStream, char, String, Date, byte[])
     */
    public static void writeFileToLiteralData(
        OutputStream out,
        char fileType,
        File file,
        byte[] buffer)
        throws IOException
    {
        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer);
        pipeFileContents(file, pOut, buffer.length);
    }

    static void pipeFileContents(File file, OutputStream pOut, int bufferSize)
        throws IOException
    {
        byte[] buf = new byte[bufferSize];

        FileInputStream in = new FileInputStream(file);
        try
        {
            int len;
            while ((len = in.read(buf)) > 0)
            {
                pOut.write(buf, 0, len);
            }

            pOut.close();
        }
        finally
        {
            Arrays.fill(buf, (byte)0);
            try
            {
                in.close();
            }
            catch (IOException ignored)
            {
                // ignore...
            }
        }
    }

    private static final int READ_AHEAD = 60;

    private static boolean isPossiblyBase64(
        int ch)
    {
        return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
            || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
            || (ch == '\r') || (ch == '\n');
    }

    /**
     * Obtains a stream that can be used to read PGP data from the provided stream.
     * 

* If the initial bytes of the underlying stream are binary PGP encodings, then the stream will * be returned directly, otherwise an {@link ArmoredInputStream} is used to wrap the provided * stream and remove ASCII-Armored encoding. *

* * @param in the stream to be checked and possibly wrapped. * @return a stream that will return PGP binary encoded data. * @throws IOException if an error occurs reading the stream, or initialising the * {@link ArmoredInputStream}. */ public static InputStream getDecoderStream( InputStream in) throws IOException { if (!in.markSupported()) { in = new BufferedInputStreamExt(in); } in.mark(READ_AHEAD); int ch = in.read(); if ((ch & 0x80) != 0) { in.reset(); return in; } else { if (!isPossiblyBase64(ch)) { in.reset(); return new ArmoredInputStream(in); } byte[] buf = new byte[READ_AHEAD]; int count = 1; int index = 1; buf[0] = (byte)ch; while (count != READ_AHEAD && (ch = in.read()) >= 0) { if (!isPossiblyBase64(ch)) { in.reset(); return new ArmoredInputStream(in); } if (ch != '\n' && ch != '\r') { buf[index++] = (byte)ch; } count++; } in.reset(); // // nothing but new lines, little else, assume regular armoring // if (count < 4) { return new ArmoredInputStream(in); } // // test our non-blank data // byte[] firstBlock = new byte[8]; System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length); try { byte[] decoded = Base64.decode(firstBlock); // // it's a base64 PGP block. // if ((decoded[0] & 0x80) != 0) { return new ArmoredInputStream(in, false); } return new ArmoredInputStream(in); } catch (DecoderException e) { throw new IOException(e.getMessage()); } } } static class BufferedInputStreamExt extends BufferedInputStream { BufferedInputStreamExt(InputStream input) { super(input); } public synchronized int available() throws IOException { int result = super.available(); if (result < 0) { result = Integer.MAX_VALUE; } return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy