2

I've been playing around with OpenPGP encryption for a little while. Here are the steps I've followed:

  • Generate an unencrypted public/private key pair in my keyring with gpg --gen-key.

  • Dump the public and private keys to files with gpg --export-secret-key -a > private.key and gpg --export -a > public.key.

  • Inspect the keys with pgpdump -i public.key and pgpdump -i private.key.

  • Use Mathematica to verify that p q == n, PowerMod[e, -1, LCM[(p - 1), (q - 1)]] == d, and PowerMod[p, -1, q] == u.

  • Encrypt a plaintext file plain.txt with gpg -e -r <my email> -a -o crypt.gpg plain.txt.

  • Decrypt the plaintext file with gpg -d crypt.gpg.

This all works fine. However, I'd like to be able to try encrypting/decrypting with keys that I generate myself: is this possible? Specifically, I'd like to know:

  • Is there a tool I can use to generate an RSA keypair from a given p, q, and e?

  • If not, and I need to implement RFC 4880 myself, what OpenPGP packets should I include? Does GPG require that I include a UserID, Signature, and Subkey packets, or could I get away with just a Public Key or Secret Key packet in each file?

    • Related: does the line, "The checksum [...] MAY appear on the first line after the base64 encoded data" mean that the base64 CRC is optional?
  • Can I get GPG to encrypt and decrypt using these keys without adding them to my keyring (as, being homebrew, they are likely to be unsecure)?

  • Finally, GPG will not let me generate keys shorter than 1024 bits. If I try to encrypt/decrypt using a custom-generated 128-bit key, will GPG allow it? What about with the 12-bit key p = 53, q = 61, e = 17 from Wikipedia's worked example?

  • Actually I just discovered that Mathematica can generate 2048-bit primes (b = 2048;RandomPrime[{2^(b - 1), 2^b - 1}]) even though PrimePi and other functions fail on primes larger than about 45 bits. However, I'm still curious is GPG enforces a limit on imported keys. – 2012rcampion May 18 '15 at 19:58
  • 1
    Those keys are not fit to perform any normal RSA scheme with padding. You need keys that are at least 27 bytes or so. At that point you might as well generate >= 1024 bit ones. – Maarten Bodewes May 18 '15 at 23:44
  • @MaartenBodewes Is that 27 bits or 27 bytes (216 bits)? – 2012rcampion May 19 '15 at 0:13
  • Bytes, being 11 bytes of padding and a secret key. – Maarten Bodewes May 19 '15 at 0:16
  • Update: it looks like Mathematica 10.1 supports a limited set of cryptographic operations (my school license is only for 10.0 unfortunately). Still doesn't include key export though. – 2012rcampion May 19 '15 at 3:02
2

Did you look at RSAKeyPairGenerator(doc)(source) from BouncyCastle? It generates an RSA keypair and encodes it. I bet you can create a keypair object, fill it with your values and call the encoding function to get a file gpg will accept.

EDIT:

So I've used openssl to generate an RSA keypair and export the values of its components:

openssl genrsa -out rsa1024.pem 1024
openssl rsa -noout -text -in rsa1024.pem

I've removed the colon byte separators and newlines from the big int values and padded the exponent to be an even number of nibbles in hex.

If you apply the following patch to this file:

--- bcpg-jdk15on-152/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java 2015-03-01 12:03:48.000000000 +0200
+++ src/foo/RSAKeyPairGenerator.java    2015-05-19 23:07:00.754583435 +0300
@@ -1,11 +1,13 @@
-package org.bouncycastle.openpgp.examples;
+package foo;

 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.KeyPair;
-import java.security.KeyPairGenerator;
+//import java.security.KeyPairGenerator;
 import java.security.NoSuchProviderException;
 import java.security.Security;
 import java.security.SignatureException;
@@ -13,6 +15,10 @@

 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPEncryptedData;
 import org.bouncycastle.openpgp.PGPException;
@@ -76,11 +82,34 @@
     {
         Security.addProvider(new BouncyCastleProvider());

-        KeyPairGenerator    kpg = KeyPairGenerator.getInstance("RSA", "BC");
+        //KeyPairGenerator    kpg = KeyPairGenerator.getInstance("RSA", "BC");

-        kpg.initialize(1024);
+        //kpg.initialize(1024);

-        KeyPair                    kp = kpg.generateKeyPair();
+        //KeyPair                    kp = kpg.generateKeyPair();
+
+        BigInteger modulus = new BigInteger("00f4035469c2adf91d69052f94a8d466cae6ea485ecfd0dc6b1d408e8ef36ef2a60d907234c4e9e8e1261e42008a41140134fc62840338433ab5948b96ceb91b65b0aacf8b54deca21a90386ebe17df16ebd1bef137ba8a43e9e2671ecd0bc5cd052321d50e61eac532cf718309f76760b9e68690897d76a5f9955541f3bd87a89", 16);
+        BigInteger publicExponent = new BigInteger("010001", 16);
+        BigInteger privateExponent = new BigInteger("00abfc6cd2de54a0f109c48df4c9ca6b6937b889a9c9effc6bc3026d78743c0eeadb44a43a6d5030c40089f31b4e56f032a995fa19f1eb05f7ab6437bee395b4a867af07b71b8515728b44fed0f5c85367e182a00a8df3df3098b82e74f224b294c90e261869c81fb9b89824bec026b7189bad0c11d4366636e2d357ca06e591c1", 16);
+        BigInteger p = new BigInteger("00fdd4b067e9361abe40231470f8249e68d64aeade1828dcd8b60cb33942df55bb6806fa81784b5fc24f73d8313baac2c0150307e56df621c77cc650b2b6996193", 16);
+        BigInteger q = new BigInteger("00f619297aa83e72f5a0c9b0df87c1bb0927c17d70cf99c6a9f6c58ddbb848d40abdd0c853baf7de55ac8dfe84ba38d12e86703e4b82273066c252369a5f3534f3", 16);
+        BigInteger dP = new BigInteger("4dbd9e69b4db85454f8f6eeb4a94ac8f9f5242acd2e970fa4e87853cbc667a737360efc847778e548cd1061dce1076a52dca47d8d4dcd56baba37183cab91f51", 16);
+        BigInteger dQ = new BigInteger("64232af0a103002e1865d955ab5cd6294c86fbeeea5a6d2efd9db7325f932accd01de355c6af5345d337d807d3ea889b80d2ad56763852068e2d7bd066cb34a7", 16);
+        BigInteger qInv = new BigInteger("00c9cbb6242b3e748d557e12cbacaa4b3e5a7fd0573fcc31b77268a492e270055628a02a46ff4cefbdff28aaea289faca9826d8e22cf5b0f4de2e15bdd4b3d3d14", 16);
+
+        RSAKeyParameters pubkey_params = new RSAKeyParameters(false, modulus, publicExponent);
+        RSAPrivateCrtKeyParameters privkey_params = new RSAPrivateCrtKeyParameters(
+               modulus, publicExponent, privateExponent, p, q, dP, dQ, qInv);
+
+        Constructor<BCRSAPublicKey> pubkeyctor = BCRSAPublicKey.class.getDeclaredConstructor(RSAKeyParameters.class);
+        pubkeyctor.setAccessible(true);
+        BCRSAPublicKey pubkey = pubkeyctor.newInstance(pubkey_params);
+       
+        Constructor<BCRSAPrivateCrtKey> privkeyctor = BCRSAPrivateCrtKey.class.getDeclaredConstructor(RSAPrivateCrtKeyParameters.class);
+        privkeyctor.setAccessible(true);
+        BCRSAPrivateCrtKey privkey = privkeyctor.newInstance(privkey_params);
+
+        KeyPair kp = new KeyPair(pubkey, privkey);

         if (args.length < 2)
         {

You'll get a program which when run with arguments "-a foo password" produces pub.asc and secret.asc files which are a pgp keypair.

EDIT 2:

$ pgpdump secret.asc 
Old: Secret Key Packet(tag 5)(510 bytes)
    Ver 4 - new
    Public key creation time - Tue May 19 22:56:20 IDT 2015
    Pub alg - RSA Encrypt or Sign(pub 1)
    RSA n(1024 bits) - ...
    RSA e(17 bits) - ...
    Sym alg - CAST5(sym 3)
    Iterated and salted string-to-key(s2k 3):
        Hash alg - SHA1(hash 2)
        Salt - 3b b9 cf f9 02 cf 90 9e 
        Count - 65536(coded count 96)
    IV - d8 ac aa 96 61 7a 03 34 
    Encrypted RSA d
    Encrypted RSA p
    Encrypted RSA q
    Encrypted RSA u
    Encrypted SHA1 hash
Old: User ID Packet(tag 13)(2 bytes)
    User ID - id
Old: Signature Packet(tag 2)(156 bytes)
    Ver 4 - new
    Sig type - Generic certification of a User ID and Public Key packet(0x10).
    Pub alg - RSA Encrypt or Sign(pub 1)
    Hash alg - SHA1(hash 2)
    Hashed Sub: signature creation time(sub 2)(4 bytes)
        Time - Tue May 19 22:56:20 IDT 2015
    Sub: issuer key ID(sub 16)(8 bytes)
        Key ID - 0xB01AC717560C7F68
    Hash left 2 bytes - ee a9 
    RSA m^d mod n(1024 bits) - ...
        -> PKCS-1
  • So far this is the best answer, but I was hoping for a more out-of-the-box solution... (plus I don't want to install Java, bleh). I can actually generate all the parameters in Mathematica so I don't even need the openssl part. But before I go to the trouble of getting this to run, I'd like to know if it generates a single key or a master key and subkey, and whether the generated key is in v3 or v4 format (I'd prefer v3 since I can more easily determine the Key ID a priori). (You can check all this with pgpdump.) – 2012rcampion May 19 '15 at 22:41
  • added pgpdump output. – Z.T. May 20 '15 at 11:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.