1

I have implemented RSA key pair generation in Java. I am generating a public and private key and then storing them as BigInteger values in files.

I have to use PKCS#1 encoding on these files. I am relatively new to cryptography. Can anyone give me a simple example how this can be done?


Note: I do may not use any external Java libraries.

For sake of completeness, I am posting the code used to generate public / private keys:

private void generateKeys(int bits, int certainty,
                                 String publicKeyFile, String secretKeyFile) throws IOException {
    p = new BigInteger(bits, certainty, new Random());

    do
        q = new BigInteger(bits, certainty, new Random());
    while (!p.gcd(q).equals(BigInteger.valueOf(1)));

    n = p.multiply(q);
    BigInteger phiN = p.subtract(BigInteger.valueOf(1)).
            multiply(q.subtract(BigInteger.valueOf(1)));

    do
        e = new BigInteger(bits, new Random());
    while (!e.gcd(phiN).equals(new BigInteger("1")));

    BigInteger d = e.modInverse(phiN);
    dp = d.mod((p.subtract(BigInteger.valueOf(1))));
    dq = d.mod((q.subtract(BigInteger.valueOf(1))));
    qinv = q.modInverse(p);

    write(secretKeyFile, n + "\n" + e + "\n" + d + "\n" + p + "\n" + q + "\n" +
            dp + "\n" + dq + "\n" + qinv);

    write(publicKeyFile, n + "\n" + e);

    System.out.println("n    = " + n.toString(16));
    System.out.println("e    = " + e.toString(16));
    System.out.println("d    = " + d.toString(16));
    System.out.println("p    = " + p.toString(16));
    System.out.println("q    = " + q.toString(16));
    System.out.println("dp   = " + dp.toString(16));
    System.out.println("dq   = " + dq.toString(16));
    System.out.println("qinv = " + qinv.toString(16));
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Zubin Kadva
  • 593
  • 1
  • 4
  • 29
  • 1
    Are you sure you don't mean PKCS#11? – user207421 Nov 01 '16 at 01:17
  • No, its PKCS#1. Is it different from ASN1, RFC3447? – Zubin Kadva Nov 01 '16 at 01:19
  • @EJP, Any thoughts?? – Zubin Kadva Nov 01 '16 at 02:55
  • PKCS 1 is a digital-signature format. Do you want to use your generated keys to perform a digital signature on a message? – pedrofb Nov 01 '16 at 06:51
  • I think you're wrong. PKCS#1 is a methods document, not a file format. ASN.1 is a syntax notation, not a file format. – user207421 Nov 01 '16 at 07:42
  • 1
    Possible duplicate of [Converting RSA keys into PKCS#1 Form from BigIntegers](http://stackoverflow.com/questions/18995687/converting-rsa-keys-into-pkcs1-form-from-bigintegers) – Roman Nov 01 '16 at 08:01
  • Agree with what @MaartenBodewes said except I think OP doesn't want encryption or signing of _data_ files using PKCS1 _schemes_, but rather _key_ files in PKCS1 _formats_. In that case note that JCE actually uses PKCS8 (unencrypted!) and 'X.509' (SubjectPublicKeyInfo) formats which _contain_ PKCS1 RSAPrivateKey and RSAPublicKey respectively. If you insist on generating the numbers yourself (why?) you can put them into JCE using `RSAPublicKeySpec` and `RSAPrivateCrtKeySpec` and a `KeyFactory` of type `RSA`. – dave_thompson_085 Nov 01 '16 at 13:07
  • 1
    Reread the question again, yes, it does seem to be a duplicate. Sorry @Roman. Unfortunately the other answer *actually* asks for a SubjectPublicKeyInfo, I'll change the title. – Maarten Bodewes Nov 01 '16 at 17:36

1 Answers1

1

OK, so you have to implement the structures in PKCS#1, the following code should work (warning, largely untested, but the ASN.1 parses):

package nl.owlstead.stackoverflow;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Random;

/**
 * Generates a key pair and then saves the key to PKCS#1 format on disk.
 * <p>
 * From <a href="https://tools.ietf.org/html/rfc3447#appendix-C">RFC 3441,
 * Appendix C</a>:
 * 
 * <pre>
 * RSAPublicKey ::= SEQUENCE {
 *     modulus           INTEGER,  -- n
 *     publicExponent    INTEGER   -- e
 * }
 * 
 * --
 * -- Representation of RSA private key with information for the CRT
 * -- algorithm.
 * --
 * RSAPrivateKey ::= SEQUENCE {
 *     version           Version,
 *     modulus           INTEGER,  -- n
 *     publicExponent    INTEGER,  -- e
 *     privateExponent   INTEGER,  -- d
 *     prime1            INTEGER,  -- p
 *     prime2            INTEGER,  -- q
 *     exponent1         INTEGER,  -- d mod (p-1)
 *     exponent2         INTEGER,  -- d mod (q-1)
 *     coefficient       INTEGER,  -- (inverse of q) mod p
 *     otherPrimeInfos   OtherPrimeInfos OPTIONAL
 * }
 * </pre>
 * 
 * @author owlstead
 *
 */
public class SimplePKCS1 {

    private static final byte SEQUENCE_TAG = 0x30;
    private static final byte INTEGER_TAG = 0x02;

    private static void writePublicKeyToPKCS1(
            String publicKeyFile,
            BigInteger n, BigInteger e) {

        try {
            ByteArrayOutputStream integerStream =
                    new ByteArrayOutputStream();
            encodeInteger(integerStream, n);
            encodeInteger(integerStream, e);
            byte[] encodedIntegers = integerStream.toByteArray();

            try (FileOutputStream fos =
                    new FileOutputStream(publicKeyFile)) {
                fos.write(SEQUENCE_TAG);
                encodeLength(fos, encodedIntegers.length);
                fos.write(encodedIntegers);
            }
        } catch (IOException ex) {
            throw new IllegalStateException("Somaliland?");
        }
    }

    private static void writePrivateKeyToPKCS1(
            String secretKeyFile,
            BigInteger n, BigInteger e, BigInteger d,
            BigInteger p, BigInteger q,
            BigInteger dp, BigInteger dq,
            BigInteger qinv) {
        try {
            ByteArrayOutputStream integerStream =
                    new ByteArrayOutputStream();
            encodeInteger(integerStream, n);
            encodeInteger(integerStream, e);
            encodeInteger(integerStream, d);
            encodeInteger(integerStream, p);
            encodeInteger(integerStream, q);
            encodeInteger(integerStream, dp);
            encodeInteger(integerStream, dq);
            encodeInteger(integerStream, qinv);
            byte[] encodedIntegers = integerStream.toByteArray();

            try (FileOutputStream fos = new FileOutputStream(secretKeyFile)) {
                fos.write(SEQUENCE_TAG);
                encodeLength(fos, encodedIntegers.length);
                fos.write(encodedIntegers);
            }
        } catch (IOException ex) {
            throw new IllegalStateException("Somaliland?");
        }
    }

    /**
     * Writes an explicit DER encoded integer (tag, length and value).
     * 
     * @param os
     *            the stream to write to
     * @param i
     *            the integer to write
     * @throws IOException
     *             any IOException thrown by the output stream
     */
    private static void encodeInteger(OutputStream os, BigInteger i)
            throws IOException {
        os.write(INTEGER_TAG);
        byte[] encodedInteger = i.toByteArray();
        encodeLength(os, encodedInteger.length);
        os.write(encodedInteger);
    }

    /**
     * Encodes a length in DER format (minimum, definite BER size).
     * 
     * @param os
     *            the stream to write to
     * @param length
     *            the length of the value to write
     * @throws IOException
     *             any IOException thrown by the output stream
     */
    private static void encodeLength(OutputStream os, int length)
            throws IOException {
        final DataOutputStream dos = new DataOutputStream(os);
        if (length < (1 << (Byte.SIZE - 1))) {
            dos.write(length);
        } else if (length < (1 << Byte.SIZE)) {
            dos.write(0x81);
            dos.write(length);
        } else if (length < (1 << Short.SIZE)) { // let's hope so :)
            dos.write(0x82);
            dos.writeShort(length);
        } else {
            throw new IllegalArgumentException(
                    "Cannot handle integers over 65535 bytes in size");
        }
    }

    /*
     * === the existing code and calls to the required functionality ===
     */

    private static void generateAndWriteKeys(
            int bits, int certainty,
            String publicKeyFile, String secretKeyFile) {
        BigInteger p = new BigInteger(bits, certainty, new Random());

        BigInteger q;
        do {
            q = new BigInteger(bits, certainty, new Random());
        } while (!p.gcd(q).equals(BigInteger.valueOf(1)));

        BigInteger n = p.multiply(q);
        BigInteger phiN = p.subtract(BigInteger.valueOf(1)).multiply(
                q.subtract(BigInteger.valueOf(1)));

        BigInteger e;
        do {
            e = new BigInteger(bits, new Random());
        } while (!e.gcd(phiN).equals(new BigInteger("1")));

        BigInteger d = e.modInverse(phiN);
        BigInteger dp = d.mod((p.subtract(BigInteger.valueOf(1))));
        BigInteger dq = d.mod((q.subtract(BigInteger.valueOf(1))));
        BigInteger qinv = q.modInverse(p);

        writePublicKeyToPKCS1(publicKeyFile, n, e);
        writePrivateKeyToPKCS1(secretKeyFile, n, e, d, p, q, dp, dq, qinv);

    }

    public static void main(String[] args) {
        generateAndWriteKeys(1024, Integer.MAX_VALUE, args[0], args[1]);
    }
}

The following methods show how to parse things back in.

private static PublicKey readPublicKeyFromPKCS1(
        String publicKeyFile) {

    try {
        try (FileInputStream fis =
                new FileInputStream(publicKeyFile)) {

            byte sequence = read(fis);
            if (sequence != SEQUENCE_TAG) {
                throw new IOException("No sequence tag found");
            }


            int length = decodeLength(fis);
            // use a CountingInputStream to check if the length of the SEQUENCE is correct

            BigInteger n = decodeInteger(fis);
            BigInteger e = decodeInteger(fis);

            return new PublicKey(n, e);
        }
    } catch (IOException ex) {
        throw new IllegalStateException("Somaliland?", ex);
    }
}

private static BigInteger decodeInteger(InputStream is) throws IOException {
    byte integer = read(is);
    if (integer != INTEGER_TAG) {
        throw new IOException("No integer tag found");
    }
    int size = decodeLength(is);
    ByteArrayOutputStream integerValueStream = new ByteArrayOutputStream(size);
    for (int i = 0; i < size; i++) {
        byte b = read(is);
        integerValueStream.write(b);
    }
    byte[] integerValue = integerValueStream.toByteArray();
    return new BigInteger(integerValue);
}

private static int decodeLength(InputStream is) throws IOException {
    int firstByte = read(is) & 0xFF;
    if (firstByte < 0x80) {
        return firstByte;
    }

    switch (firstByte) {
    case 0x80:
        throw new IOException("Invalid length");
    case 0x81:
        byte length = read(is);
        return length & 0xFF;
    case 0x82:
        int lengthHi = read(is) & 0xFF;
        int lengthLo = read(is) & 0xFF;
        return (int) (lengthHi << Byte.SIZE + lengthLo);
    default:
        throw new IOException("Length encoding unsupported");
    }
}

private static byte read(InputStream is) throws IOException {
    int x = is.read();
    if (x == -1) {
        throw new IOException("End of file reached before structure could be read");
    }
    return (byte) x;
}

The PublicKey is just a data container with an n and e field.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263