0
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?

AWS KMSとEthereumブロックチェーンの連携実装(ethers.js v6 & Web3j)

Posted at

はじめに

本記事では、AWS KMS(Key Management Service)を使用してEthereumブロックチェーンとやり取りする方法について解説します。
AWS KMSは高いセキュリティレベルでの鍵管理を提供するサービスで、秘密鍵をローカルに保存することなく安全に電子署名を行うことができます。

今回は、JavaScript(TypeScript)とJavaの両方の環境でAWS KMSを使用する方法を紹介します。

この記事で得られること

  • ethers.jsでのKMS連携実装
  • Web3jでのKMS連携実装

ethers.js

ethers.jsは、Ethereumブロックチェーンとやり取りするためのJavaScriptライブラリです。
今回はv6に対応した最新版を使用します。

使用ライブラリ

メインライブラリ:ethers.js

AWS KMS連携ライブラリ:ethers-aws-kms-signer

このライブラリは、ethers.js V6に対応しており、AWS KMSとの連携を簡単に実装できます。

インストール方法

npm install "@cuonghx.gu-tech/ethers-aws-kms-signer"

実装例

以下は、AWS KMSを使用してAwsKmsSignerを作成する基本的な実装例です。

  // AWS KMS Signerを作成
  const kmsSigner = new AwsKmsSigner(
    {
      keyId: "<KEY_ID>",
      region: "ap-northeast-1", // 東京リージョン
      credentials: {
        accessKeyId: "<AWS_ACCESS_KEY_ID>",
        secretAccessKey: "<AWS_SECRET_ACCESS_KEY>",
      },
    },
    ethers.provider as any
  );

この実装により、秘密鍵をローカル環境に保存することなく、AWS KMSの安全な環境でトランザクションに署名することができます。

  • keyId:事前にAWS KMSで作成した楕円曲線暗号キーのIDを指定
  • region:KMSキーが作成されているAWSリージョンを指定
  • credentials:適切な権限を持つAWS認証情報を指定

Web3j

Java環境では、web3jライブラリを使用してEthereumブロックチェーンとやり取りします。

残念ながら、web3jには公式のAWS KMS連携ライブラリが存在しないため、独自に実装する必要があります。
以下では、web3jのECKeyPairクラスを拡張して、AWS KMSを使用した署名機能を実装する方法を紹介します。

実装の概要

KmsECKeyPairクラスは、web3jのECKeyPairクラスを継承し、署名処理をAWS KMSで行うように拡張したクラスです。

主な機能は以下の通りです:

  1. AWS KMSからの公開鍵取得
  2. KMSを使用したECDSA署名の生成
  3. Ethereum互換の署名形式への変換

実装コード

以下は、完全なKmsECKeyPairクラスの実装です:

package com.example.demo.logic;

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.time.Duration;
import java.util.Arrays;

import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.web3j.crypto.CryptoUtils;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.ECKeyPair;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.kms.model.GetPublicKeyRequest;
import software.amazon.awssdk.services.kms.model.GetPublicKeyResponse;
import software.amazon.awssdk.services.kms.model.MessageType;
import software.amazon.awssdk.services.kms.model.SignRequest;
import software.amazon.awssdk.services.kms.model.SignResponse;
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;

/**
 * AWS KMSを使用した楕円曲線暗号キーペア実装クラス
 * 
 * Web3jのECKeyPairを拡張し、AWS KMS(Key Management Service)を使用して
 * 楕円曲線暗号による電子署名を行います。
 * 
 * 主な機能:
 * - AWS KMSからの公開鍵取得
 * - KMSを使用したECDSA署名の生成
 * 
 * @author Generated
 * @version 1.0
 */
public class KmsECKeyPair extends ECKeyPair {

    // AWS認証情報とKMS設定
    private String accessKey; // AWSアクセスキー
    private String secretKey; // AWSシークレットキー
    private String keyId; // KMSキーID
    private Region region; // AWSリージョン

    /**
     * KMSを使用するECKeyPairのコンストラクタ
     * 
     * @param accessKey AWSアクセスキー
     * @param secretKey AWSシークレットキー
     * @param keyId     KMSキーID
     * @param region    AWSリージョン
     * @throws IOException ASN.1解析時のI/Oエラー
     */
    public KmsECKeyPair(String accessKey, String secretKey, String keyId, Region region) throws IOException {
        super(BigInteger.ZERO,
                getPublicKeyByKms(accessKey, secretKey, keyId, region));
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.keyId = keyId;
        this.region = region;
    }

    @Override
    public ECDSASignature sign(byte[] transactionHash) {
        // KMSクライアントを作成
        KmsClient kmsClient = createKmsClient(accessKey, secretKey, region);

        // KMSでの署名リクエストを構築
        // 参考: https://docs.aws.amazon.com/kms/latest/APIReference/API_Sign.html
        SignRequest signRequest = SignRequest.builder()
                .keyId(keyId) // 署名するキーIDを指定
                .message(SdkBytes.fromByteArray(transactionHash)) // 署名するメッセージダイジェストを指定
                .messageType(MessageType.DIGEST) // 既にハッシュされているメッセージダイジェストには DIGESTを指定
                .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) // 署名アルゴリズムに ECDSA_SHA_256 を指定
                .build();

        try {
            // KMSで署名実行
            SignResponse signResponse = kmsClient.sign(signRequest);

            // 署名結果を取得(DER形式)
            byte[] signature = signResponse.signature().asByteArray();

            // DER形式の署名からECDSASignatureオブジェクトを作成
            ECDSASignature ecdsaSignature = CryptoUtils.fromDerFormat(signature);

            // 署名をcanonical形式に変換(Ethereumでは低いS値が必要)
            return ecdsaSignature.toCanonicalised();
        } finally {
            // KMSクライアントを閉じる
            kmsClient.close();
        }
    }

    /**
     * KMSクライアントを作成するユーティリティメソッド
     * プロキシ設定を含むHTTPクライアントを使用してKMSクライアントを構築します。
     * 
     * @param accessKey AWS アクセスキー
     * @param secretKey AWS シークレットキー
     * @param region    AWS リージョン
     * @return 構築されたKmsClientインスタンス(使用後は必ずclose()メソッドで閉じてください)
     */
    private static KmsClient createKmsClient(String accessKey, String secretKey, Region region) {
        // 認証情報を作成
        AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(accessKey, secretKey);

        // HTTPクライアントを設定
        SdkHttpClient httpClient = ApacheHttpClient.builder()
                .connectionTimeout(Duration.ofSeconds(30))
                .socketTimeout(Duration.ofSeconds(60))
                .build();

        // KMSクライアントを構築
        return KmsClient.builder()
                .region(region)
                .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
                .httpClient(httpClient)
                .build();
    }

    /**
     * AWS KMSから公開鍵を取得してBigInteger形式で返すメソッド
     * DER形式の公開鍵をASN.1構文で解析し、楕円曲線暗号の公開鍵を抽出します。
     * 
     * @param accessKey AWS アクセスキー
     * @param secretKey AWS シークレットキー
     * @param keyId     KMSキーID
     * @param region    AWS リージョン
     * @return 公開鍵のBigInteger表現
     * @throws IOException ASN.1解析時のI/Oエラー
     */
    private static BigInteger getPublicKeyByKms(String accessKey, String secretKey, String keyId, Region region)
            throws IOException {
        // KMSクライアントを作成
        KmsClient kmsClient = createKmsClient(accessKey, secretKey, region);

        try {
            // KMSでの公開鍵取得リクエストを構築
            // 参考: https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html
            GetPublicKeyRequest request = GetPublicKeyRequest.builder()
                    .keyId(keyId)
                    .build();

            // KMSから公開鍵を取得
            GetPublicKeyResponse response = kmsClient.getPublicKey(request);
            byte[] publicKeyDer = response.publicKey().asByteArray();

            // DER形式の公開鍵を解析してBigInteger形式の公開鍵を取得
            try (ASN1InputStream asn1InputStream = new ASN1InputStream(publicKeyDer)) {
                // ASN.1構文を解析
                ASN1Primitive asn1Primitive = asn1InputStream.readObject();
                // SubjectPublicKeyInfoを解析
                SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1Primitive);
                // 公開鍵のビット列を取得
                ASN1BitString publicKeyBits = publicKeyInfo.getPublicKeyData();
                byte[] publicKeyBytes = publicKeyBits.getBytes();
                // 先頭の0x04バイトを除いた部分をBigIntegerとして設定
                BigInteger publicKey = new BigInteger(1, Arrays.copyOfRange(publicKeyBytes, 1, publicKeyBytes.length));

                return publicKey;
            }
        } finally {
            // KMSクライアントを閉じる
            kmsClient.close();
        }
    }

}

実装のポイント

1. 公開鍵の取得

AWS KMSから取得する公開鍵はDER形式でエンコードされているため、ASN.1構文を使用して解析し、楕円曲線暗号の公開鍵部分を抽出する必要があります。

2. 署名の生成

KMSで生成される署名はDER形式のため、web3jのCryptoUtils.fromDerFormat()メソッドを使用して ECDSASignature 形式に変換します。

3. Canonical形式への変換

Ethereumでは、署名のS値が低い値である必要があるため、toCanonicalised()メソッドを使用して適切な形式に変換します。

4. リソース管理

KMSクライアントは使用後に必ずclose()メソッドを呼び出してリソースを解放します。

使用方法

// KmsECKeyPairインスタンスを作成
KmsECKeyPair kmsKeyPair = new KmsECKeyPair(
    "AWS_ACCESS_KEY_ID",
    "AWS_SECRET_ACCESS_KEY", 
    "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012",
    Region.AP_NORTHEAST_1
);

// Web3jでの使用例
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
Credentials credentials = Credentials.create(kmsKeyPair);

まとめ

本記事では、AWS KMSを使用してEthereumブロックチェーンとの安全なやり取りを実現する方法について、JavaScript(TypeScript)とJavaの両方の実装例を紹介しました。

主なメリット

  1. セキュリティの向上: 秘密鍵をローカル環境に保存する必要がなく、AWS KMSの高いセキュリティレベルで鍵管理を行える
  2. コンプライアンス対応: 多くの規制要件に準拠したAWS KMSを活用することで、コンプライアンス要件を満たしやすくなる
  3. 運用の簡素化: 鍵のバックアップやローテーションなどの運用業務をAWSに委ねることができる

実装時の注意点

  1. AWS権限設定: KMSキーに対する適切な権限設定が必要
  2. ネットワーク接続: KMSとの通信が必要なため、ネットワーク接続が必須
  3. コスト管理: KMS使用料金とAPI呼び出し料金が発生することを考慮する
  4. パフォーマンス: ローカル署名と比較して処理時間が長くなる可能性がある
  5. キー削除の注意: AWS KMSからは秘密鍵を取得できないため、誤ってキーを削除した場合、関連するすべての暗号資産が永続的に失われる可能性があります

AWS KMSを活用することで、Ethereumアプリケーションのセキュリティを大幅に向上させることができます。

参考(References)

0
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
0
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?