将PGP集成到Android项目中

6
我很熟悉PGP及其工作原理。我想在一个项目中添加一些功能,但是到目前为止我的调研让我感到非常困惑。我想创建一个带有定义的私人密码短语的公钥。然后,我将与某人共享公钥,他们可以使用该密钥加密消息并将其返回给我进行解密。我设想代码看起来像这样。
生成我的私人pgp密钥:
PGPKey key = new PGPKey();
key.setPassPhrase("MySecretPassword!!!1");
key.generateRandomSharedKey();
key.build();

为了加密,我会把共享密钥给我的朋友:

String encryptedText = PGPTools.Encrypt("Text to encrypt", getSharedKey());

将加密字符串发送回我后进行解密:
String decryptedText = PGPTools.Decrypt(encryptedText, key, "MySecretPassword!!!1")

显然,我知道我跳过了很多细节。通过我的研究,我看到了像Bouncy Castle和Spongy Castle这样的库的参考。非常感谢任何帮助!


看起来不错,你遇到了什么问题? - Scary Wombat
听起来很糟糕,但我想我大致上正在寻找精确的代码。我在网上找到的每个代码示例都有所不同,或者完全省略了重要部分。 - joey_g216
欢迎来到开源软件的世界 <bg> - Eugene Mayevski 'Callback
3个回答

11

我希望发布我的解决方案,因为A. 它非常难以让它工作,并且B. 如果任何加密专家想要审计我的代码,我将万分感激。

我包含了以下4个库:

compile 'com.madgag.spongycastle:core:1.50.0.0'
compile 'com.madgag.spongycastle:pg:1.50.0.0'
compile 'com.madgag.spongycastle:pkix:1.50.0.0'
compile 'com.madgag.spongycastle:prov:1.50.0.0'

需要将Bouncy Castle添加为安全提供程序。我在初始化应用程序加载时的一些其他对象的类中包含了此代码。

static {
    Security.addProvider(new BouncyCastleProvider());
}

这里是我创建的实用工具类,真正包含了核心要素。稍作编辑:

import com.example.Device;

import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.crypto.generators.RSAKeyPairGenerator;
import org.spongycastle.crypto.params.RSAKeyGenerationParameters;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPKeyRingGenerator;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.spongycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.spongycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Iterator;

public class PgpUtils {
    private static final String PROVIDER = "SC";
    private static final String KEY_RING_ID = "asdf@asdf.com";

    public static String decrypt(String encryptedText, String password) throws Exception {
        byte[] encrypted = encryptedText.getBytes();
        InputStream in = new ByteArrayInputStream(encrypted);
        in = PGPUtil.getDecoderStream(in);
        PGPObjectFactory pgpF = new PGPObjectFactory(in);
        PGPEncryptedDataList enc;
        Object o = pgpF.nextObject();
        if (o instanceof PGPEncryptedDataList) {
            enc = (PGPEncryptedDataList) o;
        } else {
            enc = (PGPEncryptedDataList) pgpF.nextObject();
        }
        PGPPrivateKey sKey = null;
        PGPPublicKeyEncryptedData pbe = null;
        while (sKey == null && enc.getEncryptedDataObjects().hasNext()) {
            pbe = (PGPPublicKeyEncryptedData)enc.getEncryptedDataObjects().next();
            sKey = getPrivateKey(getPGPSecretKeyRing(), pbe.getKeyID(), password.toCharArray());
        }
        if (pbe != null) {
            InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
            PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
            PGPCompressedData cData = (PGPCompressedData) pgpFact.nextObject();
            pgpFact = new PGPObjectFactory(cData.getDataStream());
            PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
            InputStream unc = ld.getInputStream();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int ch;
            while ((ch = unc.read()) >= 0) {
                out.write(ch);
            }
            byte[] returnBytes = out.toByteArray();
            out.close();
            return new String(returnBytes);
        }
        return null;
    }

    private static PGPPublicKey getPublicKey(PGPPublicKeyRing publicKeyRing) {
        Iterator<?> kIt = publicKeyRing.getPublicKeys();
        while (kIt.hasNext()) {
            PGPPublicKey k = (PGPPublicKey) kIt.next();
            if (k.isEncryptionKey()) {
                return k;
            }
        }
        return null;
    }

    private static PGPPrivateKey getPrivateKey(PGPSecretKeyRing keyRing, long keyID, char[] pass) throws PGPException {
        PGPSecretKey secretKey = keyRing.getSecretKey(keyID);
        PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
        return secretKey.extractPrivateKey(decryptor);
    }

    public static String encrypt(String msgText) throws IOException, PGPException {
        byte[] clearData = msgText.getBytes();
        PGPPublicKey encKey = getPublicKey(getPGPPublicKeyRing());
        ByteArrayOutputStream encOut = new ByteArrayOutputStream();
        OutputStream out = new ArmoredOutputStream(encOut);
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedDataGenerator.ZIP);
        OutputStream cos = comData.open(bOut);
        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream pOut = lData.open(cos, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, clearData.length, new Date());
        pOut.write(clearData);
        lData.close();
        comData.close();
        PGPEncryptedDataGenerator encGen =
                new PGPEncryptedDataGenerator(
                        new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setWithIntegrityPacket(true).setSecureRandom(
                                new SecureRandom()).setProvider(PROVIDER));
        if (encKey != null) {
            encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider(PROVIDER));
            byte[] bytes = bOut.toByteArray();
            OutputStream cOut = encGen.open(out, bytes.length);
            cOut.write(bytes);
            cOut.close();
        }
        out.close();
        return new String(encOut.toByteArray());
    }

    public final static PGPKeyRingGenerator generateKeyRingGenerator (char[] pass) throws PGPException{
        RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), 2048, 12));
        PGPKeyPair rsakp_sign = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpg.generateKeyPair(), new Date());
        PGPKeyPair rsakp_enc = new BcPGPKeyPair(PGPPublicKey.RSA_ENCRYPT, kpg.generateKeyPair(), new Date());
        PGPSignatureSubpacketGenerator signhashgen = new PGPSignatureSubpacketGenerator();
        signhashgen.setKeyFlags(false, KeyFlags.SIGN_DATA|KeyFlags.CERTIFY_OTHER|KeyFlags.SHARED);
        signhashgen.setPreferredSymmetricAlgorithms(false, new int[]{SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128});
        signhashgen.setPreferredHashAlgorithms(false, new int[]{HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA224});
        signhashgen.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
        PGPSignatureSubpacketGenerator enchashgen = new PGPSignatureSubpacketGenerator();
        enchashgen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
        PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
        PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256);
        PBESecretKeyEncryptor pske = (new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha256Calc, 0xc0)).build(pass);
        PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator (PGPSignature.POSITIVE_CERTIFICATION, rsakp_sign,
                KEY_RING_ID, sha1Calc, signhashgen.generate(), null, new BcPGPContentSignerBuilder(rsakp_sign.getPublicKey().getAlgorithm(),
                HashAlgorithmTags.SHA1), pske);
        keyRingGen.addSubKey(rsakp_enc, enchashgen.generate(), null);
        return keyRingGen;
    }

    private static PGPPublicKeyRing getPGPPublicKeyRing() throws IOException {
        ArmoredInputStream ais = new ArmoredInputStream(new ByteArrayInputStream(Device.getDevice().getPgpPublicKey().getBytes()));
        return (PGPPublicKeyRing) new PGPObjectFactory(ais).nextObject();
    }

    private static PGPSecretKeyRing getPGPSecretKeyRing() throws IOException {
        ArmoredInputStream ais = new ArmoredInputStream(new ByteArrayInputStream(Device.getDevice().getPgpSecretKey().getBytes()));
        return (PGPSecretKeyRing) new PGPObjectFactory(ais).nextObject();
    }

    public final static String genPGPPublicKey (PGPKeyRingGenerator krgen) throws IOException {
        ByteArrayOutputStream baosPkr = new ByteArrayOutputStream();
        PGPPublicKeyRing pkr = krgen.generatePublicKeyRing();
        ArmoredOutputStream armoredStreamPkr = new ArmoredOutputStream(baosPkr);
        pkr.encode(armoredStreamPkr);
        armoredStreamPkr.close();
        return new String(baosPkr.toByteArray(), Charset.defaultCharset());
    }

    public final static String genPGPPrivKey (PGPKeyRingGenerator krgen) throws IOException {
        ByteArrayOutputStream baosPriv = new ByteArrayOutputStream ();
        PGPSecretKeyRing skr = krgen.generateSecretKeyRing();
        ArmoredOutputStream armoredStreamPriv = new ArmoredOutputStream(baosPriv);
        skr.encode(armoredStreamPriv);
        armoredStreamPriv.close();
        return new String(baosPriv.toByteArray(), Charset.defaultCharset());
    }
}

这是我创建私钥和公钥的方法:

final PGPKeyRingGenerator krgen = PgpUtils.generateKeyRingGenerator("password".toCharArray());
String pgpPublicKey = PgpUtils.genPGPPublicKey(krgen);
String pgpSecretKey = PgpUtils.genPGPPrivKey(krgen);

最后,使用自己的公钥进行加密和解密:

String encrypted = PgpUtils.encrypt("message text");
String decrypted = PgpUtils.decrypt(encrypted, "Password");

谢谢,它起作用了。只需要对解密进行小修改,因为加密消息在服务器端没有被压缩。 - Fenil
有没有想法如何在iOS上实现类似的开源功能? - Fenil
2
我基于joey_g216的解决方案创建了一个Android库:https://github.com/kibotu/Android-PGP - Jan Rabe

2

我没有足够的声望在 joey_g216 上面优秀回答中进行评论。起初,它对我有用,但是在尝试解密各种文件时失败了。这是因为 PGPObject 的结构可能会发生变化。

为了使解密工作正常,我必须做出调整:

public static String decrypt(String encryptedText, String password) throws Exception {

需要包括:

// Could be
// signature + compressed -> data
// signature + data
// data
// compressed -> data

Object z = pgpFact.nextObject();
while (!(z instanceof PGPLiteralData))
{

    if (z instanceof PGPCompressedData) {
        PGPCompressedData cData = (PGPCompressedData) z;
        pgpFact = new PGPObjectFactory(cData.getDataStream(), bcKeyFingerprintCalculator);
    }

    if (z instanceof PGPOnePassSignatureList) {
        // ignore for now!
    }

    z = pgpFact.nextObject();

}
PGPLiteralData ld = (PGPLiteralData) z;

1

1
是的,非常接近。对于我来说,找到如何定义密码的代码示例几乎是不可能的。 - joey_g216
@JoeGoble 很高兴能够帮助! - Dannie
BC是一个庞大的库,将其集成到您的应用程序中应该是最后的选择。 - Jeffrey Blattman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接