How to sign a message using the GOST R 34.11 / 34.10-2001 algorithm in Java

    In this article I will tell you how to sign an arbitrary message with a private key and a certificate according to the GOST R 34.11 / 34.10-2001 algorithm with an attached signature in the Java language.

    For the electronic document management project, I needed to make a signature using the GOST algorithm. Despite the fact that he appeared a long time ago, to my surprise, I could not find a single completed example on the network that would receive a message, key and certificate on the input, and would give a signed message on the output.

    All the examples found either used third-party paid CryptoPro software, or did not compile with modern versions of Java, or signed messages were then not validated.

    In general, I spent a lot of time to figure it out, and decided that a complete ready-made example would be useful to someone.

    To sign, we need a certificate and a private key.
    They were given to me in pfx format, the components must be extracted from it.

    I did everything on windows and used the OpenSsl build with GOST support. For other operating systems, I think the actions will be similar. In OpenSsl from version 1.1.0, the built-in support for GOST was removed, it must be connected in a confused way, which I did not take off on the move. So I just downloaded the old version 1.0.2.

    Add the following lines to the openssl.cfg config:

    openssl_conf = openssl_def
    [openssl_def]
    engines = engine_section
    [engine_section]
    gost = gost_section
    [gost_section]
    engine_id = gost
    dynamic_path = gost.dll
    default_algorithms = ALL
    CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

    We launch the console and enter the command (without it, I didn’t have the config):

    set OPENSSL_CONF=C:\папка_с_openssl\bin\openssl.cfg

    Export the key to pkcs12 format:

    openssl pkcs12 -in my.pfx -nocerts -nodes -out my.pem

    We translate the key into pkcs8 format:

    openSSL pkcs8 -in my.pem -topk8 -nocrypt -out key.pk8

    Export certificate:

    openssl pkcs12 -in my.pfx -nokeys -out my.cer

    When executing the commands, a password from pfx will be requested, of course, you need to know it.

    To sign in Java, I used the BouncyCastle library, it supports GOST.
    I have a project on Maven, I added dependencies in pom.xml:

    org.bouncycastlebcprov-jdk15on1.59org.bouncycastlebcpkix-jdk15on1.59

    Signing Method Code:

    public static byte[] signWithGost3410(byte[] data, X509Certificate certificate, byte[] encodedPrivateKey) throws Exception {
            X509Certificate[] certificates = new X509Certificate[1];
            certificates[0] = certificate;
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
            KeyFactory keyFactory = KeyFactory.getInstance("ECGOST3410", "BC");
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
            CMSTypedData msg = new CMSProcessableByteArray(data);
            Store certStore = new JcaCertStore(Arrays.asList(certificates));
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            ContentSigner signer = new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("GOST3411withECGOST3410").setProvider("BC").build(privateKey);
            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(signer, (X509Certificate) certificates[0]));
            gen.addCertificates(certStore);
            CMSSignedData sigData = gen.generate(msg, true);
            return sigData.getEncoded();
        }

    The code for the method of reading the key from the file:

    public static byte[] readEncodedKeyFromPk8File(String filename) throws Exception {
        byte[] content = Files.readAllBytes(Paths.get(filename));
        ArrayList lines = new ArrayList<>(Arrays.asList(new String(content).split("\n")));
        lines.remove(0);
        lines.remove(lines.size() -1);
        String base64 = String.join("", lines);
        byte[] encoded = Base64.getDecoder().decode(base64);
        return encoded;
    }

    The code for the method of reading a certificate from a file:

    public static X509Certificate readX509CertificateFromCerFile(String filename) throws Exception {
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        Certificate certificate = factory.generateCertificate(new FileInputStream(filename));
        return (X509Certificate) certificate;
    }

    And finally, an example signature:

    @Test
    public void signTest() throws Exception{
        Security.addProvider(new BouncyCastleProvider());
        byte[] key = readEncodedKeyFromPk8File("key.pk8");
        X509Certificate certificate = readX509CertificateFromCerFile("my.cer");
        byte[] data = Files.readAllBytes(Paths.get("my.xml"));
        byte[] signedData = signWithGost3410(data, certificate, key);
        try(FileOutputStream stream = new FileOutputStream("signed.dat")){
            stream.write(signedData);
        }
    }

    The resulting .dat file passes the signature verification successfully, for example, here .

    Hope this example is helpful. If necessary, to translate into another language, I think, will not be difficult.

    Also popular now: