Search code examples
javaencodingbouncycastleasn.1

Export ASN.1 format, then encode with Base64 JAVA


I want to make a custom ASN.1 structure, what consists of 3 PrintableString's and 1 OctetString. I am using BouncyCastle framework to work with this.

So I set in my class the needed parameters, and now I have to return this structure in ASN.1 format, and then encode it with Base64 (its parameter is byte[]), then into PEM format.

So my question is, that what type of object do I have to return from method getASN1format()?

My code:

import org.bouncycastle.asn1.*;
import java.io.IOException;

public class ASN1Handshake1 implements ASN1Encodable {
    private DERPrintableString A, B, ID_PASS;
    private ASN1OctetString ID_K;

    public ASN1Handshake1(String A, String B, String ID_K, String ID_PASS, TTP TTPs ) throws IOException {
        this.A = new DERPrintableString(A);
        this.B = new DERPrintableString(B);
        this.ID_K = new DEROctetString(ID_K.getBytes());
        this.ID_PASS = new DERPrintableString(ID_PASS);
    }

    public ?? getASN1format(){
        //TODO
    }

    @Override
    public ASN1Primitive toASN1Primitive() {
        return null;
    }
}

Solution

  • I'm using Bouncy Castle 1.57 (bcprov-jdk15on) for this code.

    First of all, keep in mind that ASN.1 is not a format per se, it's a description language that defines a structure, and PEM is a format that uses base 64. Many cryptography standards use ASN.1 to define their data structures, and PEM or DER (Distinguished Encoding Rules) to serialize those structures.

    So, if you want to get the ASN.1 structure and format it to base64, you can do as below. You don't need a getASN1format method, just use the existent ones.

    The fields can't just be "loose" in the ASN.1 structure. So I decided to put them in a sequence (using org.bouncycastle.asn1.DERSequence class), which is the best choice to store fields of a structure. I put them in the order they're declared, but of course you can choose any order you want.

    I've also changed the variables names to follow Java's code conventions (names start with lowercase letters). So the class code is:

    import org.bouncycastle.asn1.ASN1Encodable;
    import org.bouncycastle.asn1.ASN1Object;
    import org.bouncycastle.asn1.ASN1OctetString;
    import org.bouncycastle.asn1.ASN1Primitive;
    import org.bouncycastle.asn1.ASN1Sequence;
    import org.bouncycastle.asn1.DEROctetString;
    import org.bouncycastle.asn1.DERPrintableString;
    import org.bouncycastle.asn1.DERSequence;
    
    public class ASN1Handshake1 extends ASN1Object {
    
        private DERPrintableString a, b, idPass;
    
        private ASN1OctetString idK;
    
        // removed TTPs parameter (it wasn't using it)
        public ASN1Handshake1(String a, String b, String idK, String idPass) {
            this.a = new DERPrintableString(a);
            this.b = new DERPrintableString(b);
            this.idK = new DEROctetString(idK.getBytes());
            this.idPass = new DERPrintableString(idPass);
        }
    
        // returns a DERSequence containing all the fields
        @Override
        public ASN1Primitive toASN1Primitive() {
            ASN1Encodable[] v = new ASN1Encodable[] { this.a, this.b, this.idK, this.idPass };
            return new DERSequence(v);
        }
    }
    

    To create a handshake object and convert it to base64 (the code below is not handling exceptions, so add the try/catch block accordingly):

    import org.bouncycastle.util.encoders.Base64;
    
    // create handshake object with some sample data
    ASN1Handshake1 handshake = new ASN1Handshake1("a", "b", "ID_K", "ID_PASS");
    
    // convert it to base64
    String base64String = new String(Base64.encode(handshake.getEncoded()));
    System.out.println(base64String);
    

    This will output the handshake structure in base64 format:

    MBUTAWETAWIEBElEX0sTB0lEX1BBU1M=

    Please note that this is not a complete PEM (with headers like -----BEGIN CERTIFICATE-----) because your custom structure is not a predefined standard. So, you'll have to stay with this base64 generic string.

    To check that the base64 string contains the ASN.1 sequence, just do:

    // read from base64 String
    ASN1Sequence seq = (ASN1Sequence) DERSequence.fromByteArray(Base64.decode(base64String.getBytes()));
    int n = seq.size();
    for (int i = 0; i < n; i++) {
        ASN1Encodable obj = seq.getObjectAt(i);
        if (obj instanceof DEROctetString) {
            System.out.println(new String(((DEROctetString) obj).getOctets()));
        } else {
            System.out.println(obj);
        }
    }
    

    The output is:

    a
    b
    ID_K
    ID_PASS