17
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NRI OpenStandiaAdvent Calendar 2021

Day 6

楕円曲線暗号の鍵をJavaで読み込む

Last updated at Posted at 2021-12-06

OpenSSLで生成した楕円曲線暗号の鍵をJavaで読み込む方法。RSAはあるけど楕円曲線が無かったので。

手順

OpenSSLで鍵生成

$ openssl ecparam -genkey -name secp256r1 -out keypair.pem
$ openssl ec -in keypair.pem -outform PEM -pubout -out public.pem
$ openssl ec -in keypair.pem -outform PEM -out private2.pem
$ openssl pkcs8 -topk8 -nocrypt -in private2.pem -out private.pem

できたもの

  • public.pem:公開鍵
  • private.pem:秘密鍵(PKCS#8形式)

いらないもの

  • keypair.pem
  • private2.pem

ポイント1:-name で指定する楕円曲線の一覧は openssl ecparam -list_curves で出力できる。
ポイント2:秘密鍵はPKCS#8形式に変換する(重要!)

Javaで読み込み

  • 秘密鍵
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class Main {

    public static void main(String[] args) throws Exception {
        var kf = KeyFactory.getInstance("EC");

        var privatePem = readPem("private.pem"); // ①
        var privateDer = Base64.getDecoder().decode(privatePem); // ②
        
        var privateKeySpec = new PKCS8EncodedKeySpec(privateDer);
        var privatekey = (ECPrivateKey) kf.generatePrivate(privateKeySpec); // ➂
        System.out.println(privatekey.getParams());
    }

    private static String readPem(String filename) throws IOException {
        return Files.readString(Paths.get(filename))
                 .replaceAll("-----.+?-----", "")
                 .replaceAll("\\r?\\n", "")
                 .trim();
    }
}

ポイント①:いつものように、PEMのヘッダー、フッター、改行を取る。
ポイント②:Base64デコードして、DER(バイナリ)形式に変換する。
ポイント➂:秘密鍵の読み込み。PKCS#8に変換しないと、次のようなエラーがでる。

Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
	at jdk.crypto.ec/sun.security.ec.ECKeyFactory.engineGeneratePrivate(ECKeyFactory.java:170)
	at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:389)
	at test5.Main.main(Main.java:22)
Caused by: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
	at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:135)
	at java.base/sun.security.pkcs.PKCS8Key.<init>(PKCS8Key.java:95)
	at jdk.crypto.ec/sun.security.ec.ECPrivateKeyImpl.<init>(ECPrivateKeyImpl.java:75)
	at jdk.crypto.ec/sun.security.ec.ECKeyFactory.implGeneratePrivate(ECKeyFactory.java:245)
	at jdk.crypto.ec/sun.security.ec.ECKeyFactory.engineGeneratePrivate(ECKeyFactory.java:166)
	... 2 more
  • 公開鍵
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class Main {

    public static void main(String[] args) throws Exception {
        var kf = KeyFactory.getInstance("EC");

        var publicPem = readPem("public.pem"); // ①
        var publicDer = Base64.getDecoder().decode(publicPem); // ②
        
        var publicKeySpec = new X509EncodedKeySpec(publicDer);
        var publicKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
        System.out.println(publicKey);
    }

    private static String readPem(String filename) throws IOException {
        return Files.readString(Paths.get(filename))
                 .replaceAll("-----.+?-----", "")
                 .replaceAll("\\r?\\n", "")
                 .trim();
    }
}

ポイント①:いつものように、PEMのヘッダー、フッター、改行を取る。
ポイント②:Base64デコードして、DER(バイナリ)形式に変換する。

おまけ - JWTの署名と検証

Nimbus使うので、事前にいれておいてください。

  • JWT生成(署名)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyUse;

public class Main {

    public static void main(String[] args) throws Exception {
        var kf = KeyFactory.getInstance("EC");

        var publicPem = readPem("public.pem");
        var publicDer = Base64.getDecoder().decode(publicPem);
        
        var publicKeySpec = new X509EncodedKeySpec(publicDer);
        var publicKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
        System.out.println(publicKey);
        
        var jwk = new ECKey(Curve.P_256, publicKey, privatekey, KeyUse.SIGNATURE, null, null, null, null, null, null, null, null); // ①
        var signer = new ECDSASigner(jwk);
        var jwt = new JWSObject(
                new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(jwk.getKeyID()).build(),
                new Payload("{...}"));
        jwt.sign(signer);
        var str = jwt.serialize();
        System.out.println(str);
    }
    
    private static String readPem(String filename) throws IOException {
        return Files.readString(Paths.get(filename))
                .replaceAll("-----.+?-----", "")
                .replaceAll("\\r?\\n", "")
                .trim();
    }
}
  • JWTパーズ(検証)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyUse;

public class Main {

    public static void main(String[] args) throws Exception {
        var kf = KeyFactory.getInstance("EC");

        var privatePem = readPem("private2.pem");
        var privateDer = Base64.getDecoder().decode(privatePem);
        
        var privateKeySpec = new PKCS8EncodedKeySpec(privateDer);
        var privatekey = (ECPrivateKey) kf.generatePrivate(privateKeySpec);
        System.out.println(privatekey.getParams());
        
        var jwk = new ECKey(Curve.P_256, publicKey, privatekey, KeyUse.SIGNATURE, null, null, null, null, null, null, null, null); // ①
        
        var str = "...(JWTの文字列)...";
        var jwt = JWSObject.parse(str);
        var verifier = new ECDSAVerifier(jwk);
        jwt.verify(verifier);
        System.out.println(jwt);      
    }
    
    private static String readPem(String filename) throws IOException {
        return Files.readString(Paths.get(filename))
                .replaceAll("-----.+?-----", "")
                .replaceAll("\\r?\\n", "")
                .trim();
    }
}

ポイント①-1:楕円曲線の種類は、OpenSSLで生成した曲線名に合わせる。この記事では secp256r1 (P-256) なので、P_256 を指定。
ポイント①-2:署名だけでも、検証だけでも、公開鍵と秘密鍵の両方が必要になる。

17
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?