Tanda Tangan Digital digunakan dimana-mana, seperti misalnya para pemasok Amazon Vendor Central mengirimkan faktur ke as2.amazonsedi.com, pun juga para pemasok Ritel Walmart mengirim Advance Shipping Notice ke gem.wal-mart.com:5080, para pemborong mengirimkan Pemberitahuan Impor Barang ke PT. EDI Indonesia menggunakan aplikasi eXpert eXchange, penandatanganan berkas PDF dengan aplikasi Adobe Acrobat Reader, penandatanganan surel di Microsoft Outlook, Zimbra, dan Mozilla Thunderbird, penandatangan APK yang akan diunggah ke Google Play Store, dan tentu saja, para pengguna mata uang bitcoin dengan blockchain.info.

Cukup segitu saja cerita-ceritanya, mari mulai belajar cara memverifikasi tanda tangan digital dengan algoritma ecdsa-with-SHA256 (oid 1.2.840.10045.4.3.2) dan kurva eliptik secp256k1 seperti yang dipakai bitcoin: https://en.bitcoin.it/wiki/Secp256k1. Wujud konkritnya, kurva eliptik secp256k1 digunakan ketika membuat alamat wallet (public key) beserta pasangan kunci privatnya. Yah baru segitu saja pengetahuan penulis, kode sumber berikut akan mendemonstrasikan verifikasi tanda tangan digital sebuah transaksi bitcoin (https://blockchain.info/tx/211b8fb30990632751a83d1dc4f0323ff7d2fd3cad88084de13c9be2ae1c6426), pada tautan tersebut terdapat tanda tangan digital beserta kunci publik untuk melakukan verifikasi.

Yah langsung saja, sila pembaca mengunduh berkas bcprov-jdk15on-159.jar, salin kode sumber berikut ke dalam editor kode sumber kesukaan anda, simpan berkas dengan nama ScriptSigVerification.java

//berkas ScriptSigVerification.java
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.*;

/**
 *
 * @author dawud_tan
 */
public class ScriptSigVerification {

    public static void main(String args[]) throws Exception {
        //https://blockchain.info/tx/211b8fb30990632751a83d1dc4f0323ff7d2fd3cad88084de13c9be2ae1c6426?format=hex
        byte[] rawBitcoinTransaction = DatatypeConverter.parseHexBinary(
                "01000000"
                + "01"
                + "be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d5396"
                + "00000000"
                + "1976a914dd6cce9f255a8cc17bda8ba0373df8e861cb866e88ac"
                + "ffffffff"
                + "01"
                + "23ce010000000000"
                + "1976a9142bc89c2702e0e618db7d59eb5ce2f0f147b4075488ac"
                + "00000000"
                + "01000000");

        SHA256Digest digest = new SHA256Digest();
        digest.reset();
        digest.update(rawBitcoinTransaction, 0, rawBitcoinTransaction.length);
        byte[] content = new byte[digest.getDigestSize()];
        digest.doFinal(content, 0);

        //https://blockchain.info/tx/211b8fb30990632751a83d1dc4f0323ff7d2fd3cad88084de13c9be2ae1c6426?show_adv=true#tx-70378065
        byte[] signature = DatatypeConverter.parseHexBinary(
                "30"
                + "45"
                + "022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed553b1"
                + "02205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e");

        ASN1Sequence s = (ASN1Sequence) ASN1Primitive.fromByteArray(signature);
        BigInteger[] sig = new BigInteger[2];
        sig[0] = ((ASN1Integer) s.getObjectAt(0)).getValue();
        sig[1] = ((ASN1Integer) s.getObjectAt(1)).getValue();

        digest.reset();
        digest.update(content, 0, content.length);
        byte[] tobeVerified = new byte[digest.getDigestSize()];
        digest.doFinal(tobeVerified, 0);
        System.out.println("hasil: " + verifyData(tobeVerified, sig[0], sig[1]));
    }

    private static boolean verifyData(byte[] hash, BigInteger r, BigInteger s) throws Exception {
        ECPublicKeyParameters key = generatePublicKeyParameter(getSignerPublicKey());
        ECDomainParameters ec = key.getParameters();
        BigInteger n = ec.getN();
        BigInteger e = calculateE(n, hash);
        BigInteger c = s.modInverse(n);
        BigInteger u1 = e.multiply(c).mod(n);
        BigInteger u2 = r.multiply(c).mod(n);
        ECPoint G = ec.getG();
        ECPoint Q = key.getQ();
        ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2);
        if (point.isInfinity()) {
            return false;
        }
        ECCurve curve = point.getCurve();
        if (curve != null) {
            BigInteger cofactor = curve.getCofactor();
            if (cofactor != null && cofactor.compareTo(BigInteger.valueOf(8)) <= 0) {
                ECFieldElement D = point.getZCoord(0).square();//Jacobian Coordinates
                if (D != null && !D.isZero()) {
                    ECFieldElement X = point.getXCoord();
                    while (curve.isValidFieldElement(r)) {
                        ECFieldElement R = curve.fromBigInteger(r).multiply(D);
                        if (R.equals(X)) {
                            return true;
                        }
                        r = r.add(n);
                    }
                    return false;
                }
            }
        }
        BigInteger v = point.normalize().getAffineXCoord().toBigInteger().mod(n);
        return v.equals(r);
    }

    private static BigInteger calculateE(BigInteger n, byte[] message) {
        int log2n = n.bitLength();
        int messageBitLength = message.length * 8;
        BigInteger e = new BigInteger(1, message);
        if (log2n < messageBitLength) {
            e = e.shiftRight(messageBitLength - log2n);
        }
        return e;
    }

    private static ECPublicKeyParameters generatePublicKeyParameter(BCECPublicKey key) {
        ECParameterSpec s = key.getParameters();
        return new ECPublicKeyParameters(
                key.getQ(),
                new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
    }

    private static BCECPublicKey getSignerPublicKey() throws CertificateException, NoSuchProviderException, IOException {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(
                "MIICpTCCAkugAwIBAgIEQ4798zAKBggqhkjOPQQDAjCBrjEmMCQGCSqGSIb3DQEJ"
                + "ARYXcm9zZXR0YW5ldEBtZW5kZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI"
                + "DAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEiMCAGA1UECgwZbWVuZGVsc29uLWUt"
                + "Y29tbWVyY2UgR21iSDEiMCAGA1UECwwZbWVuZGVsc29uLWUtY29tbWVyY2UgR21i"
                + "SDENMAsGA1UEAwwEbWVuZDAeFw0wNTEyMDEwNjQzMTVaFw0xOTA4MTAwNjQzMTVa"
                + "MIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkG"
                + "A1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMSIwIAYD"
                + "VQQKDBltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLDBltZW5kZWxz"
                + "b24tZS1jb21tZXJjZSBHbWJIMQ0wCwYDVQQDDARtZW5kMFYwEAYHKoZIzj0CAQYF"
                + "K4EEAAoDQgAELaqTMV7rviy5tcNQXfTG+2ysqLdWeGCYVnVQ1IIMCduYj+mZfQSd"
                + "aHKS+BXM1uf7XBsakRN5mYGNF8c9D4Cu+aNYMFYwJwYDVR0lBCAwHgYIKwYBBQUH"
                + "AwEGCCsGAQUFBwMCBggrBgEFBQcDBDAMBgNVHQ8EBQMDB4mAMB0GA1UdDgQWBBR0"
                + "5huY8iW76wirvvqg56FYj4AiwzAKBggqhkjOPQQDAgNIADBFAiEAyDLjBoq4Sfpc"
                + "Mpom5drIcIFF/jFHORx6GWu/Lyx58iICIGWbl6lmtXzELX1doYvb1WfGRcj0alvJ"
                + "XMooVAaRixmq"))) {
            return (BCECPublicKey) new CertificateFactory()
                    .engineGenerateCertificate(bis).getPublicKey();
        }
    }
}

Buka program terminal atau command prompt, lakukan kompilasi program dengan menjalankan perintah javac -cp bcprov-jdk15on-159.jar ScriptSigVerification.java, lalu jalankan program tersebut dengan menjalankan perintah java -cp "bcprov-jdk15on-159.jar:." ScriptSigVerification, bila anda menggunakan sistem operasi Windows, tanda titik dua : (colon), diganti menjadi titik koma ; (semicolon). Berikut luaran program tersebut di atas.

Verifikasi Tandan Tangan Digital PKCS7

Salin kode sumber berikut ke dalam editor kode sumber kesukaan anda, simpan berkas dengan nama EllipticCurveDigitalSignatureAlgorithm.java

//berkas EllipticCurveDigitalSignatureAlgorithm.java
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.*;
import org.bouncycastle.util.Arrays;

/**
 *
 * @author dawud_tan
 */
public class EllipticCurveDigitalSignatureAlgorithm {

    public static void main(String args[]) throws Exception {
        byte[] rawSignedAttrs = DatatypeConverter.parseHexBinary(
                "31"//tag SET
                + "81" + "95"
                + "30"//DL Sequence
                + "18"
                + "06092A864886F70D010903"//1.2.840.113549.1.9.3 contentType
                + "310B06092A864886F70D010701"//1.2.840.113549.1.7.1 data
                + "30"//DL Sequence
                + "1C"
                + "06092A864886F70D010905"//signingTime
                + "310F"
                + "170D3134313132373133323133365A"//UTCTime//DatatypeConverter.printHexBinary(new Time(new Date()).getEncoded())
                + "30"//DL Sequence
                + "2A"
                + "06092A864886F70D010934"//id-aa-CMSAlgorithmProtection
                + "311D"//DLSet
                + "301B"//DLSequence
                + "300D"//DLSequence
                + "0609608648016503040201" + "0500"//2.16.840.1.101.3.4.2.1
                + "A10A"
                + "06082A8648CE3D040302"//1.2.840.10045.4.3.2
                //
                + "30"//DL Sequence
                + "2F"
                + "06092A864886F70D010904"//messageDigest
                + "31"//set
                + "22"
                + "04"//octet string
                + "20"//octet string length
                + "F3C8D1B1D986D4F3CA7D6C6F68B528950EFD425BE23EE577A62851E84D69EB12"
        );

        DLSet setOfSignedAttribute = (DLSet) new ASN1InputStream(rawSignedAttrs).readObject();

        DLSequence cmsAlgorithmProtectAttribute = (DLSequence) setOfSignedAttribute.getObjectAt(2);
        DLSet setOfProtectedAttribute = (DLSet) cmsAlgorithmProtectAttribute.getObjectAt(1);
        DLSequence digestAlgorithmAttribute = (DLSequence) setOfProtectedAttribute.getObjectAt(0);
        AlgorithmIdentifier digestAlgorithm = AlgorithmIdentifier.getInstance(digestAlgorithmAttribute.getObjectAt(0));

        byte[] content = ("Content-Type: text/plain; charset=UTF-8\r\n"
                + "Content-Transfer-Encoding: 7bit\r\n\r\n"
                + "Halo Dunia").getBytes(StandardCharsets.UTF_8);

        SHA256Digest digest = new SHA256Digest();
        digest.reset();
        digest.update(content, 0, content.length);
        byte[] contentHash = new byte[digest.getDigestSize()];
        digest.doFinal(contentHash, 0);

        DLSequence messageDigestAttribute = (DLSequence) setOfSignedAttribute.getObjectAt(3);
        DLSet setOfMessageDigest = (DLSet) messageDigestAttribute.getObjectAt(1);
        DEROctetString receivedMessageDigest = (DEROctetString) setOfMessageDigest.getObjectAt(0);
        if (!Arrays.constantTimeAreEqual(contentHash,
                receivedMessageDigest.getOctets())) {
            throw new Exception("message-digest attribute value does not match calculated value");
        }

        byte[] signature = DatatypeConverter.parseHexBinary(
                "30"
                + "45"
                + "02210084D09E60AFB2B27BD43537CAAFF20D984181022E3D007A6446543A85F687B7D3"
                + "0220361E925D55CEE8616846D7DF85C4B3253E944E7C5D74610550E8AC08AE6A5D41");

        ASN1Sequence s = (ASN1Sequence) ASN1Primitive.fromByteArray(signature);
        BigInteger[] sig = new BigInteger[2];
        sig[0] = ((ASN1Integer) s.getObjectAt(0)).getValue();
        sig[1] = ((ASN1Integer) s.getObjectAt(1)).getValue();

        digest.reset();
        digest.update(rawSignedAttrs, 0, rawSignedAttrs.length);
        byte[] tobeVerified = new byte[digest.getDigestSize()];
        digest.doFinal(tobeVerified, 0);
        System.out.println("hasil: " + verifyData(tobeVerified, sig[0], sig[1]));
    }

    private static boolean verifyData(byte[] hash, BigInteger r, BigInteger s) throws Exception {
        ECPublicKeyParameters key = generatePublicKeyParameter(getSignerPublicKey());
        ECDomainParameters ec = key.getParameters();
        BigInteger n = ec.getN();
        BigInteger e = calculateE(n, hash);
        BigInteger c = s.modInverse(n);
        BigInteger u1 = e.multiply(c).mod(n);
        BigInteger u2 = r.multiply(c).mod(n);
        ECPoint G = ec.getG();
        ECPoint Q = key.getQ();
        ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2);
        if (point.isInfinity()) {
            return false;
        }
        ECCurve curve = point.getCurve();
        if (curve != null) {
            BigInteger cofactor = curve.getCofactor();
            if (cofactor != null && cofactor.compareTo(BigInteger.valueOf(8)) <= 0) {
                ECFieldElement D = point.getZCoord(0).square();//Jacobian Coordinates
                if (D != null && !D.isZero()) {
                    ECFieldElement X = point.getXCoord();
                    while (curve.isValidFieldElement(r)) {
                        ECFieldElement R = curve.fromBigInteger(r).multiply(D);
                        if (R.equals(X)) {
                            return true;
                        }
                        r = r.add(n);
                    }
                    return false;
                }
            }
        }
        BigInteger v = point.normalize().getAffineXCoord().toBigInteger().mod(n);
        return v.equals(r);
    }

    private static BigInteger calculateE(BigInteger n, byte[] message) {
        int log2n = n.bitLength();
        int messageBitLength = message.length * 8;
        BigInteger e = new BigInteger(1, message);
        if (log2n < messageBitLength) {
            e = e.shiftRight(messageBitLength - log2n);
        }
        return e;
    }

    private static ECPublicKeyParameters generatePublicKeyParameter(BCECPublicKey key) {
        ECParameterSpec s = key.getParameters();
        return new ECPublicKeyParameters(
                key.getQ(),
                new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
    }

    private static BCECPublicKey getSignerPublicKey() throws CertificateException, NoSuchProviderException, IOException {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(
                "MIICpTCCAkugAwIBAgIEQ4798zAKBggqhkjOPQQDAjCBrjEmMCQGCSqGSIb3DQEJ"
                + "ARYXcm9zZXR0YW5ldEBtZW5kZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI"
                + "DAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEiMCAGA1UECgwZbWVuZGVsc29uLWUt"
                + "Y29tbWVyY2UgR21iSDEiMCAGA1UECwwZbWVuZGVsc29uLWUtY29tbWVyY2UgR21i"
                + "SDENMAsGA1UEAwwEbWVuZDAeFw0wNTEyMDEwNjQzMTVaFw0xOTA4MTAwNjQzMTVa"
                + "MIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkG"
                + "A1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMSIwIAYD"
                + "VQQKDBltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLDBltZW5kZWxz"
                + "b24tZS1jb21tZXJjZSBHbWJIMQ0wCwYDVQQDDARtZW5kMFYwEAYHKoZIzj0CAQYF"
                + "K4EEAAoDQgAELaqTMV7rviy5tcNQXfTG+2ysqLdWeGCYVnVQ1IIMCduYj+mZfQSd"
                + "aHKS+BXM1uf7XBsakRN5mYGNF8c9D4Cu+aNYMFYwJwYDVR0lBCAwHgYIKwYBBQUH"
                + "AwEGCCsGAQUFBwMCBggrBgEFBQcDBDAMBgNVHQ8EBQMDB4mAMB0GA1UdDgQWBBR0"
                + "5huY8iW76wirvvqg56FYj4AiwzAKBggqhkjOPQQDAgNIADBFAiEAyDLjBoq4Sfpc"
                + "Mpom5drIcIFF/jFHORx6GWu/Lyx58iICIGWbl6lmtXzELX1doYvb1WfGRcj0alvJ"
                + "XMooVAaRixmq"))) {
            return (BCECPublicKey) new CertificateFactory()
                    .engineGenerateCertificate(bis).getPublicKey();
        }
    }
}

Buka program terminal atau command prompt, lakukan kompilasi program dengan menjalankan perintah javac -cp bcprov-jdk15on-159.jar EllipticCurveDigitalSignatureAlgorithm.java, lalu jalankan program tersebut dengan menjalankan perintah java -cp "bcprov-jdk15on-159.jar:." EllipticCurveDigitalSignatureAlgorithm, bila anda menggunakan sistem operasi Windows, tanda titik dua : (colon), diganti menjadi titik koma ; (semicolon). Berikut luaran program tersebut di atas.

Contoh tanda tangan digital sebuah pesan dengan format SignedData (1.2.840.113549.1.7.2)

Program java di atas, mengambil contoh dari tanda tangan digital berikut. Tanda tangan digital disandikan dengan Base64.

-----BEGIN PKCS7-----
MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAADGCAb0w
ggG5AgEBMIG3MIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkG
A1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMSIwIAYDVQQKDBltZW5k
ZWxzb24tZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLDBltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJI
MQ0wCwYDVQQDDARtZW5kAgRDjv3zMA0GCWCGSAFlAwQCAQUAoIGVMBgGCSqGSIb3DQEJAzELBgkq
hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE0MTEyNzEzMjEzNlowKgYJKoZIhvcNAQk0MR0wGzAN
BglghkgBZQMEAgEFAKEKBggqhkjOPQQDAjAvBgkqhkiG9w0BCQQxIgQg88jRsdmG1PPKfWxvaLUo
lQ79QlviPuV3pihR6E1p6xIwCgYIKoZIzj0EAwIERzBFAiEAhNCeYK+ysnvUNTfKr/INmEGBAi49
AHpkRlQ6hfaHt9MCIDYekl1VzuhhaEbX34XEsyU+lE58XXRhBVDorAiual1BAAAAAAAA
-----END PKCS7-----
SignedData berisi tanda tangan digital milik sebuah entitas.

Bila penyandian Base64 tersebut dikonversi menjadi heksadesimal, menjadi seperti berikut
308006092A864886F70D010702A0803080020101310F300D06096086480165030402010500308006092A864886F70D0107010000318201BD308201B90201013081B73081AE3126302406092A864886F70D0109011617726F73657474616E6574406D656E64656C736F6E2E6465310B3009060355040613024445310F300D06035504080C064265726C696E310F300D06035504070C064265726C696E31223020060355040A0C196D656E64656C736F6E2D652D636F6D6D6572636520476D624831223020060355040B0C196D656E64656C736F6E2D652D636F6D6D6572636520476D6248310D300B06035504030C046D656E640204438EFDF3300D06096086480165030402010500A08195301806092A864886F70D010903310B06092A864886F70D010701301C06092A864886F70D010905310F170D3134313132373133323133365A302A06092A864886F70D010934311D301B300D06096086480165030402010500A10A06082A8648CE3D040302302F06092A864886F70D01090431220420F3C8D1B1D986D4F3CA7D6C6F68B528950EFD425BE23EE577A62851E84D69EB12300A06082A8648CE3D0403020447304502210084D09E60AFB2B27BD43537CAAFF20D984181022E3D007A6446543A85F687B7D30220361E925D55CEE8616846D7DF85C4B3253E944E7C5D74610550E8AC08AE6A5D41000000000000

Signed Attributes

Bagian

(31) 81
     95

    (30) 18
         06092A864886F70D010903
        (31) 0B
             06092A864886F70D010701

    (30) 1C
         06092A864886F70D010905
        (31) 0F
             170D3134313132373133323133365A

    (30) 2A
         06092A864886F70D010934
        (31) 1D
            (30) 1B
                (30) 0D
                     06096086480165030402010500
                    (A1) 0A
                         06082A8648CE3D040302

   (30) 2F
        06092A864886F70D010904
       (31) 22
           (04) 20
                F3C8D1B1D986D4F3CA7D6C6F68B528950EFD425BE23EE577A62851E84D69EB12
adalah signedAttrs dari pesan penanda tangan, menurut RFC 5652, bagian 5.3, kolom ini bertipe SET SIZE (1..MAX) OF Attribute. Penandanya adalah nilai Tag pada byte pertama yaitu 0x31.

Pembaca bisa mendapatkan deretan nilai heksadesimal Signed Attributes tersebut dengan cara sebagai berikut.

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.digests.SHA256Digest;

/**
 *
 * @author dawud_tan
 */
public class SignedAttrs {
    public static void main(String[] args) throws Exception {
        byte[] content = ("Content-Type: text/plain; charset=UTF-8\r\n"
                + "Content-Transfer-Encoding: 7bit\r\n\r\n"
                + "Halo Dunia").getBytes(StandardCharsets.UTF_8);
        Hashtable<ASN1ObjectIdentifier, Attribute> std = new Hashtable<>();
        Attribute attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.3"),
                new DERSet(new ASN1ObjectIdentifier("1.2.840.113549.1.7.1")));
        std.put(attr.getAttrType(), attr);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.5"),
                new DERSet(new Time(new Date())));
        std.put(attr.getAttrType(), attr);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.52"),
                new DERSet(new CMSAlgorithmProtection(
                        new AlgorithmIdentifier(new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1"), DERNull.INSTANCE),
                        1,
                        new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.10045.4.3.2")))));
        std.put(attr.getAttrType(), attr);
        SHA256Digest digest = new SHA256Digest();
        digest.reset();
        digest.update(content, 0, content.length);
        byte[] messageDigest = new byte[digest.getDigestSize()];
        digest.doFinal(messageDigest, 0);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.4"),
                new DERSet(new DEROctetString(messageDigest)));
        std.put(attr.getAttrType(), attr);
        DERSet signedAttr = new DERSet(new AttributeTable(std).toASN1EncodableVector());
        System.out.println(DatatypeConverter.printHexBinary(signedAttr.getEncoded(ASN1Encoding.DER)));
    }
}

Signature Value

Bagian berikutnya,

(30) 45
    (02) 21
         0084D09E60AFB2B27BD43537CAAFF20D984181022E3D007A6446543A85F687B7D3
    (02) 20
         361E925D55CEE8616846D7DF85C4B3253E944E7C5D74610550E8AC08AE6A5D41
adalah Signature Value, menurut RFC 5753 bagian 7.2 memiliki tipe data ECDSA-Sig-Value. Tipe data ECDSA-Sig-Value adalah SEQUENCE dari dua INTEGER. Tipe data SEQUENCE memiliki nilai Tag 0x30, sedangkan INTEGER memiliki nilai Tag 0x02.

Pembaca bisa mendapatkan deretan nilai heksadesimal Signature Value tersebut dengan cara sebagai berikut.

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.ECDH;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.*;

/**
 *
 * @author dawud_tan
 */
public class ECDSASigning {

    public static void main(String[] args) throws Exception {
        byte[] content = ("Content-Type: text/plain; charset=UTF-8\r\n"
                + "Content-Transfer-Encoding: 7bit\r\n\r\n"
                + "Halo Dunia").getBytes(StandardCharsets.UTF_8);
        byte[] signedAttrs = getSignedAttrs(content);
        ECKeyParameters key = generatePrivateKeyParameter(getSignerPrivateKey());
        SHA256Digest digest = new SHA256Digest();
        digest.reset();
        digest.update(signedAttrs, 0, signedAttrs.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        ECDomainParameters ec = key.getParameters();
        BigInteger n = ec.getN();
        BigInteger e = calculateE(n, hash);
        BigInteger d = ((ECPrivateKeyParameters) key).getD();
        RandomDSAKCalculator kCalculator = new RandomDSAKCalculator();
        SecureRandom random = new SecureRandom();
        kCalculator.init(n, random);
        BigInteger r, s;
        ECMultiplier basePointMultiplier = new FixedPointCombMultiplier();
        // https://tools.ietf.org/html/rfc6090#section-5.3.2
        do // generate s
        {
            BigInteger k;
            do // generate r
            {
                k = kCalculator.nextK();
                ECPoint p = basePointMultiplier.multiply(ec.getG(), k).normalize();
                // https://tools.ietf.org/html/rfc6090#section-5.3.3
                r = p.getAffineXCoord().toBigInteger().mod(n);
            } while (r.equals(BigInteger.valueOf(0L)));
            s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
        } while (s.equals(BigInteger.valueOf(0L)));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        System.out.println(DatatypeConverter.printHexBinary(new DERSequence(v).getEncoded(ASN1Encoding.DER)));
    }

    static ECPrivateKeyParameters generatePrivateKeyParameter(
            BCECPrivateKey k) {
        ECParameterSpec s = k.getParameters();
        return new ECPrivateKeyParameters(
                k.getD(),
                new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
    }

    static byte[] getSignedAttrs(byte[] content) throws IOException {
        Hashtable<ASN1ObjectIdentifier, Attribute> std = new Hashtable<>();
        Attribute attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.3"),
                new DERSet(new ASN1ObjectIdentifier("1.2.840.113549.1.7.1")));
        std.put(attr.getAttrType(), attr);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.5"),
                new DERSet(new Time(new Date())));
        std.put(attr.getAttrType(), attr);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.52"),
                new DERSet(new CMSAlgorithmProtection(
                        new AlgorithmIdentifier(new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1"), DERNull.INSTANCE),
                        1,
                        new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.10045.4.3.2")))));
        std.put(attr.getAttrType(), attr);
        SHA256Digest digest = new SHA256Digest();
        digest.reset();
        digest.update(content, 0, content.length);
        byte[] messageDigest = new byte[digest.getDigestSize()];
        digest.doFinal(messageDigest, 0);
        attr = new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.4"),
                new DERSet(new DEROctetString(messageDigest)));
        std.put(attr.getAttrType(), attr);
        DERSet signedAttr = new DERSet(new AttributeTable(std).toASN1EncodableVector());
        return signedAttr.getEncoded(ASN1Encoding.DER);
    }

    static BigInteger calculateE(BigInteger n, byte[] message) {
        int log2n = n.bitLength();
        int messageBitLength = message.length * 8;
        BigInteger e = new BigInteger(1, message);
        if (log2n < messageBitLength) {
            e = e.shiftRight(messageBitLength - log2n);
        }
        return e;
    }

    static BCECPrivateKey getSignerPrivateKey() throws Exception {
        ECDH keyFactory = new ECDH();
        return (BCECPrivateKey) keyFactory.generatePrivate(PrivateKeyInfo.getInstance(Base64.getDecoder().decode(
                "MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgDs0gZUwuK+cISVhT6No1"
                + "xmQkcEDAC9ELmxPl6G5qgI2hRANCAAS6ccrrUSe+JnDmmWd2cU3MS9oPMLXOxkaw"
                + "/zwsoIKTXn89tWlCygjosYHQZwl7RttTvyi4fcUoAeJdIejU/83I")));
    }
}

bersambung >.<

Reactions:

You Might Also Like:

Berikan Komentar Sembunyikan Komentar

Hello, how may we help you? Just send us a message now to get assistance.

Facebook Messenger ×