楕円曲線暗号の署名検証をJavaで実装してみる


暗号理論の中で、RSAと同じ公開鍵暗号に属する楕円曲線暗号(Elliptic Curve Cryptography: ECC)ですが、RSAちゃんよりできる子と言われ続けて早15年くらい?
このまま秘密兵器のまま終わるのかと思いきや、ようやっと使われ始める兆しがあったりなかったりなので、Javaでの実装方法について調べてみました。
Javaでは、JDK 7以上で楕円曲線暗号のネイティブ・プロバイダが追加されたようです。

サンプルコード

楕円曲線DSA(Elliptic Curve Digital Signature Algorithm: ECDSA)での、署名検証サンプルコードです。
鍵ペアの生成、署名の作成、署名の検証を順に実施しています。
実際に使う場合には、署名作成する人と署名検証をする人は別の人になりますが、サンプルでは同じメソッド内でやってます。

package com.example;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;

import javax.xml.bind.DatatypeConverter;

public class ECDSAExample {

    /**
     * 楕円曲線DSA 署名検証サンプル
     */
    public static void main(String[] args) throws Exception {
        /*
         *  楕円曲線暗号 鍵ペア生成
         */
        // 鍵ペア生成器
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); // Elliptic Curve
        // 乱数生成器
        SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
        // 鍵サイズと乱数生成器を指定して鍵ペア生成器を初期化
        int keySize = 256;
        keyGen.initialize(keySize, randomGen);

        // 鍵ペア生成
        KeyPair keyPair = keyGen.generateKeyPair();
        // 秘密鍵
        PrivateKey privateKey = keyPair.getPrivate();
        // 公開鍵
        PublicKey publicKey = keyPair.getPublic();

        /*
         * 署名生成
         */
        String originalText = "This is string to sign";

        // 署名生成アルゴリズムを指定する
        Signature dsa = Signature.getInstance("SHA1withECDSA");
        // 初期化
        dsa.initSign(privateKey);
        // 署名生成
        dsa.update(originalText.getBytes("UTF-8"));
        // 生成した署名を取り出す
        byte[] signature = dsa.sign();
        System.out.println("Signature: " + DatatypeConverter.printHexBinary(signature));

        /*
         * 署名検証
         */
        // 初期化
        dsa.initVerify(publicKey);
        // 署名検証する対象をセットする
        dsa.update(originalText.getBytes("UTF-8"));
        // 署名検証
        boolean verifyResult = dsa.verify(signature);
        System.out.println("Verify: " + verifyResult);
    }
}

KeyPairGeneratorアルゴリズム

鍵ペアジェネレータの引数には、他に以下のものを指定することが出来ます。

アルゴリズム名 説明
DiffieHellman Diffie-Hellman KeyAgreementアルゴリズムの鍵ペアを生成します。
注: key.getAlgorithm()は、「DiffieHellman」ではなく「DH」を返します。
DSA デジタル署名アルゴリズムの鍵ペアを生成します。
RSA RSAアルゴリズム(Signature/Cipher)の鍵ペアを生成します。
EC Elliptic Curveアルゴリズムの鍵ペアを生成します。

SecureRandom乱数生成アルゴリズム

乱数生成ジェネレータの引数には、他に以下のものを指定することが出来ます。

アルゴリズム名 説明
NativePRNG 基盤となるネイティブOSから乱数を取得します。乱数生成のブロック性については何も表明されません。
NativePRNGBlocking 基盤となるネイティブOSから乱数を取得し、必要に応じてブロック化します。たとえば、UNIX系システムの/dev/randomなど。
NativePRNGNonBlocking 基盤となるネイティブOSから乱数を取得しますが、アプリケーションの速度低下を避けるためブロック化しません。たとえば、UNIX系システムの/dev/urandomなど。
PKCS11 基盤となるインストールおよび構成済みのPKCS11ライブラリから乱数を取得します。
SHA1PRNG Sunプロバイダが提供する擬似乱数生成(PRNG)アルゴリズム。このアルゴリズムは、PRNGの基盤としてSHA-1を使用します。各操作につき値が1増加する64ビット・カウンタを使って鎖状につながった真にランダムなシード値から、SHA-1ハッシュを計算します。160ビットのSHA-1出力のうち、64ビットだけが使用されます。
Windows-PRNG 基盤となるWindows OSから乱数を取得します。

Signatureアルゴリズム

署名アルゴリズムには、他に以下のものを指定することが出来ます。

アルゴリズム名 説明
NONEwithRSA RSA操作を行う前にダイジェスト・アルゴリズム(MD5/SHA1など)を使用しないRSA署名アルゴリズム。RSA署名アルゴリズムについては、PKCS#1を参照してください。
MD2withRSA
MD5withRSA
PKCS#1で定義された、RSA暗号を使用したMD2/MD5署名アルゴリズム。MD2/MD5ダイジェスト・アルゴリズムおよびRSAを使用してRSAデジタル署名を作成および検証します。
SHA1withRSA
SHA224withRSA
SHA256withRSA
SHA384withRSA
SHA512withRSA
OSI Interoperability Workshopで定義された、SHA-*およびRSA暗号化アルゴリズムを使用した署名アルゴリズム。PKCS#1に記述されているパディング規則を使用します。
NONEwithDSA FIPS PUB 186-2で定義されたデジタル署名アルゴリズム。このデータの長さは正確に20バイトである必要があります。このアルゴリズムは、rawDSAとも呼ばれています。
SHA1withDSA
SHA224withDSA
SHA256withDSA
FIPS PUB 186-3で定義された、SHA-1、SHA-224またはSHA-256ダイジェスト・アルゴリズムを使用してデジタル署名を作成および検証するDSA署名アルゴリズム。
NONEwithECDSA
SHA1withECDSA
SHA224withECDSA
SHA256withECDSA
SHA384withECDSA
SHA512withECDSA
(ECDSA)
ANSI X9.62で定義されたECDSA署名アルゴリズム。
注:「ECDSA」は「SHA1withECDSA」アルゴリズムのあいまいな名前であるため、使用しないでください。代わりに、正式な名前「SHA1withECDSA」を使用します。
<digest>with<encryption> この形式を使用して、特定のメッセージ・ダイジェスト(MD2、MD5など)とアルゴリズム(RSA、DSAなど)を使用する署名アルゴリズムの名前を指定します。このセクションで紹介した明示的に定義されている標準名(MD2withRSAなど)も同じ形式で指定されています。
PKCS#1 v2.0で定義された新しい署名方式の場合は、<digest>with<encryption>の形式では不十分なため、<digest>with<encryption>and<mgf>の形式を使用して名前を指定できます。<mgf>は、MGF1などのマスク生成機能に置き換える必要があります。例: MD5withRSAandMGF1。

参考元

java - Tutorial of ECDSA algorithm to sign a string - Stack Overflow
https://stackoverflow.com/questions/11339788/tutorial-of-ecdsa-algorithm-to-sign-a-string
Java暗号化アーキテクチャ標準アルゴリズム名のドキュメント(JDK 8用) - #KeyPairGeneratorアルゴリズム
http://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator
Java暗号化アーキテクチャ標準アルゴリズム名のドキュメント(JDK 8用) - #SecureRandom乱数生成アルゴリズム
http://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/StandardNames.html#SecureRandom
Java暗号化アーキテクチャ標準アルゴリズム名のドキュメント(JDK 8用) - #Signatureアルゴリズム
http://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/StandardNames.html#Signature
「Java 暗号化アーキテクチャ API 仕様 & リファレンス」の付録 A(PRNG アルゴリズムの標準的な名前)
https://docs.oracle.com/javase/jp/1.4/guide/security/CryptoSpec.html#AppA
Elliptical curve cryptography in java
http://rahulatjava.blogspot.jp/2014/02/elliptical-curve-cryptography-in-java.html
bc-java/ECIESTest.java at master · bcgit/bc-java
https://github.com/bcgit/bc-java/blob/master/prov/src/test/java/org/bouncycastle/jce/provider/test/ECIESTest.java
Encryption and Decryption of Data using Elliptic Curve Cryptography( ECC ) with Bouncy Castle C# Library
https://www.codeproject.com/Tips/1071190/Encryption-and-Decryption-of-Data-using-Elliptic-C