Sassyブログ

好きなことで暮らしを豊かにするブログ

Javaで秘密鍵を使用してRSA256、HMAC256、ECDSA256それぞれの暗号化を行う実装サンプルについて

f:id:y_saiki:20171025000902j:plain

目次

 

1.環境

 

2. OpenSSLのインストール

以下のOpenSSLのサイトにアクセスして、インストーラーをダウンロードしてインストールを完了させる。
 

3.今回の対象の暗号化アルゴリズム

ちなみにjavaでは.pem形式の鍵ファイルは読み込めないため、pkcs形式に変換する必要がある。
 

3-1.【RSASSA-PKCS1-v1_5 using SHA-256(RS256)】

  ※RS256は非対称アルゴリズムであり、公開鍵/秘密鍵のペアを使用する。
 
  ★コマンド
openssl genrsa -out rs256.key 2048
 
  [公開鍵]
openssl rsa -pubout < rs256.key > rs256.pub.key
 
  pkcs8形式に変換
openssl pkcs8 -in rs256.key -out rs256.key.pkcs8 -topk8 -nocrypt
  

 3-2.【HMAC using SHA-256(HS256)】

  •  対称アルゴリズムであり、2つの当事者間で共有される1つの(秘密)鍵のみを使用する。
  •  ハッシュ計算対象の文字列を秘密鍵として、暗号化時と複合化時にソルトとして使用する。
  •  鍵ファイル生成などの必要はなし。
  •  互いに参照できる場所に秘密鍵を持たせる必要がある。
     HMACについては、与えられた秘密鍵の元になる値を使用してハッシュ生成して秘密鍵ハッシュ値)を作成する。
 

3-3.【ECDSA using P-256 and SHA-256(ES256)】

★コマンド
秘密鍵と公開鍵生成のもとになるペアファイルを作成する
openssl ecparam -genkey -name prime256v1 -noout -out es256-key-pair.key
 
[公開鍵]
openssl ec -in es256-key-pair.key -outform PEM -pubout -out es256.pub.key
 
openssl ec -in es256-key-pair.key -outform PEM -out es256.key
 
pkcs8形式に変換
openssl pkcs8 -in es256.key -out es256.key.pkcs8 -topk8 -nocrypt
 
  

4. 暗号化アルゴリズムを用いた暗号化と検証のjavaプログラムサンプル

Algorithm.java
/**
 * 署名と署名検証によるアルゴリズムを管理する
 */
public abstract class Algorithm {
 
       /** 署名アルゴリズム名 */
       private String algName;
 
       /** アルゴリズム種別 */
       private String alg;
 
       /**
       * ロガー.
       */
       private static Logger LOGGER = LoggerFactory.getLogger(Algorithm.class);
 
       /**
       * コンストラクタ(直接new不可)
       * @param algName 署名アルゴリズム
       * @param alg アルゴリズム種別
       */
       protected Algorithm(String algName, String alg) {
            this.algName = algName;
            this.alg = alg;
       }
 
       /**
       * 署名オブジェクトの生成
       * @param alg アルゴリズム種別
       * @param service サービス
       * @param clientId クライアントID
       * @return 署名インスタンス
       */
       public static Algorithm createSignatureObject(String alg, String sercret) {
 
       Algorithm algorithm = null;
 
       switch (alg) {
 
            case "HS256":
                 LOGGER.debug("Generate the secret key (hash value) of HS 256 used for signature...");
 
                  //ハッシュ値生成に使用する値に変更する
                 byte secretKey = sercret.getBytes();
                 algorithm = Algorithm.HMAC256(secretKey);
                 break;
 
            case "RS256":
                 LOGGER.debug("Read the secret key of RS 256...");
 
                 RSAPrivateKey rsaPrivateKey = null;
                
                 try {
                      rsaPrivateKey = (RSAPrivateKey)
                      KeyFactory.getInstance("RSA").generatePrivate(
                      new PKCS8EncodedKeySpec(readKey(★ファイルパス)));
 
                 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                      LOGGER.debug(e.getMessage(), e);
                 }
 
                 algorithm = Algorithm.RSA256(rsaPrivateKey);
                 break;
 
            case "ES256":
                 LOGGER.debug("Read the secret key of ES 256...");
 
                 ECPrivateKey esPrivateKey = null;
                 try {
                      esPrivateKey = (ECPrivateKey)
                      KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(
                      readKey(★ファイルパス)));
 
                 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                      LOGGER.debug(e.getMessage(), e);
                 }
 
                 algorithm = Algorithm.ECDSA256(esPrivateKey);
                 break;
            }
            return algorithm;
      }
 
       /**
       * 署名検証オブジェクトの生成
       * @param alg アルゴリズム種別
       * @param service サービス
       * @param clientId クライアントID
       * @return 署名検証インスタンス
       */
       public static Algorithm createVerificationObject(String alg, String sercret){
 
            Algorithm algorithm = null;
 
            switch (alg) {
 
            case "HS256":
 
                 LOGGER.debug("Generate the secret key (hash value) of HS 256 used for verification...");
 
                 byte secretKey = sercret.getBytes();
 
                 algorithm = Algorithm.HMAC256(secretKey);
                 break;
 
            case "RS256":
                 LOGGER.debug("Read the public key of RS 256...");
 
                 RSAPublicKey rsaPublicKey = null;
                 
                 try {
                      rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
                      .generatePublic(new X509EncodedKeySpec(★ファイルパス)));
 
                 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                      LOGGER.debug(e.getMessage(), e);
                 }
                 algorithm = Algorithm.RSA256(rsaPublicKey);
                 break;
 
            case "ES256":
                 LOGGER.debug("Read the public key of ES 256...");
 
                 ECPublicKey esPublicKey = null;
                 try {
                      esPublicKey = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(
                      readKey(★ファイルパス)));
 
                 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                      LOGGER.debug(e.getMessage(), e);
                 }
                 algorithm = Algorithm.ECDSA256(esPublicKey);
                 break;
            }
 
            return algorithm;
       }
 
       /**
       * アルゴリズム名を取得する
       * @return algoName
       */
       public String getAlgName() {
            return algName;
       }
 
       /**
       * アルゴリズム種別を取得する
       * @return alg
       */
       public String getAlg() {
            return alg;
       }
 
       /**
       * 署名を行う
       * @param content 署名対象のByte配列
       * @return
       */
       public abstract byte sign(byte content);
 
       /**
       * 署名検証を行う
       * @param content 署名検証対象のByte配列
       * @param signature 署名検証に使用する署名
       * @return
       */
       public abstract boolean verify(byte content, byte signature);
 
       /**
       * HMAC256で署名と署名検証を行うためのアルゴリズムインスタンスを返却する。
       * @param key 鍵のByte配列
       * @return HMAC256アルゴリズムインスタンス
       */
       private static Algorithm HMAC256(byte key) {
            return new HmacAlgorithm("HS256", "HmacSHA256", key);
       }
 
       /**
       * RSA256で署名と署名検証を行うためのアルゴリズムインスタンスを返却する。
       * @param key 鍵のByte配列
       * @return RSA256アルゴリズムインスタンス
       */
       private static Algorithm RSA256(RSAKey key) {
            return new RsaAlgorithm("RS256", "SHA256withRSA", key);
       }
 
       /**
       * ECDSA256で署名と署名検証を行うためのアルゴリズムインスタンスを返却する。
       * @param key 鍵のByte配列
       * @return ECDSA256アルゴリズムインスタンス
       */
       private static Algorithm ECDSA256(ECKey key) {
            return new EcdsaAlgorithm("ES256", "SHA256withECDSA", key);
       }
 
       /**
       * 鍵ファイルを読み込む
       * @param fileName 読み込み対象ファイル名
       * @return
       */
       private static byte readKey(final String fileName) {
 
            byte keyBytes = null;
 
            BufferedReader br = null;
            try {
                 InputStream keyStream = new FileInputStream(fileName);
                 LOGGER.debug("★keyStream -> " + keyStream);
 
                 br = new BufferedReader(new InputStreamReader(keyStream));
 
                 String line;
                 StringBuilder sb = new StringBuilder();
                 boolean isContents = false;
 
                 while ((line = br.readLine()) != null) {
                    if (line.matches("[-]+BEGIN[ A-Z]+[-]+")) {
                         //内容がある。内容存在フラグを立てておく。
                         isContents = true;
                     } else if (line.matches("[-]+END[ A-Z]+[-]+")) {
                          //内容の終了行であるため、ループを抜ける
                          break;
                     } else if (isContents) {
                         //読み込んだ内容を連結する
                         sb.append(line);
                     }
                 }
                 keyBytes = Base64.getDecoder().decode(sb.toString());
            } catch (IOException e) {
                 LOGGER.debug(e.getMessage(), e);
            } finally {
                try {
                       br.close();
                } catch (IOException e) {
                       LOGGER.debug(e.getMessage(), e);
                }
       }
       return keyBytes;
   }
}
 
EcdsaAlgorithm.java
/**
 * ECDSAアルゴリズムを用いた署名生成と署名検証を管理する
 */
public class EcdsaAlgorithm extends Algorithm {
 
     /** EC鍵 */
     private ECKey key;
 
     /** ロガー*/
     private static Logger LOGGER = LoggerFactory.getLogger(EcdsaAlgorithm.class);
 
     /**
     * コンストラク
     * @param algName アルゴリズム
     * @param alg アルゴリズム種別
     * @param key 鍵
     */
     public EcdsaAlgorithm(String algName, String alg, ECKey key) {
          super(algName, alg);
          this.key = key;
     }
 
     @Override
     public byte sign(byte contentByte) {
 
          //*******************************************
          //ECDSAで署名を行う
          //*******************************************
          byte encryptedContent = null;
 
          if (key == null) {
               LOGGER.debug("秘密鍵が存在しない");
               return encryptedContent;
          }
 
          if (!(this.key instanceof PrivateKey)) {
               return encryptedContent;
          }
 
          try {
               Signature signature = Signature.getInstance(getAlg());
               signature.initSign(
                   (PrivateKey) this.key
               );
               signature.update(contentByte);
               encryptedContent = signature.sign();
 
          } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
               LOGGER.debug(e.getMessage(), e);
          }
 
          return encryptedContent;
     }
 
     @Override
     public boolean verify(byte contentByte, byte signatureByte) {
 
          //*******************************************
          //ECDSAで署名検証を行う
          //*******************************************
          boolean isValid = false;
 
          if (key == null) {
               LOGGER.debug("公開鍵が存在しない");
               return isValid;
          }
 
          if (!(this.key instanceof PublicKey)) {
               return isValid;
          }
 
          try {
               Signature signature = Signature.getInstance(getAlg());
               signature.initVerify(
                   (PublicKey) this.key
               );
               signature.update(contentByte);
               isValid = signature.verify(signatureByte);
          } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
               LOGGER.debug(e.getMessage(), e);
          }
          return isValid;
     }
}
 
HmacAlgorithm.java
/**
 * HMACアルゴリズムを用いた署名生成と署名検証を管理する
 */
public class HmacAlgorithm extends Algorithm {
 
     /** 鍵 */
     private byte key;
 
     /** ロガー*/
     private static Logger LOGGER = LoggerFactory.getLogger(HmacAlgorithm.class);
 
     /**
     * コンストラク
     * @param algName アルゴリズム
     * @param alg アルゴリズム種別
     * @param key 鍵
     */
     public HmacAlgorithm(String algName, String alg, byte key) {
          super(algName, alg);
          this.key = key;
     }
 
     @Override
     public byte sign(byte contentByte) {
 
          //*******************************************
          //HMACで署名を行う
          //*******************************************
          byte encryptedContent = null;
 
          if (key == null) {
               LOGGER.debug("秘密鍵が存在しない");
               return encryptedContent;
          }
 
          try {
               Mac mac = Mac.getInstance(getAlg());
               mac.init(new SecretKeySpec(key, getAlg()));
               encryptedContent = mac.doFinal(contentByte);
          } catch (NoSuchAlgorithmException | InvalidKeyException e) {
               LOGGER.debug(e.getMessage(), e);
          }
          return encryptedContent;
     }
 
     @Override
     public boolean verify(byte contentByte, byte signatureByte) {
 
          //*******************************************
          //HMACで署名検証を行う
          //*******************************************
          boolean isValid = false;
 
          if (key == null) {
               LOGGER.debug("秘密鍵が存在しない");
               return isValid;
          }
 
          try {
               Mac mac = Mac.getInstance(getAlg());
               mac.init(new SecretKeySpec(key, getAlg()));
               byte encryptedContent = mac.doFinal(contentByte);
               isValid = MessageDigest.isEqual(encryptedContent, signatureByte);
          } catch (NoSuchAlgorithmException | InvalidKeyException e) {
               LOGGER.debug(e.getMessage(), e);
          }
 
          return isValid;
     }
}
 
RsaAlgorithm.java
/**
 * RSAアルゴリズムを用いた署名生成と署名検証を管理する
 */
public class RsaAlgorithm extends Algorithm {
 
     /** RSA鍵 */
     private RSAKey key;
 
     /** ロガー */
     private static Logger LOGGER = LoggerFactory.getLogger(RsaAlgorithm.class);
 
     /**
     * コンストラク
     * @param algName アルゴリズム
     * @param alg アルゴリズム種別
     * @param key 鍵
     */
     public RsaAlgorithm(String algName, String alg, RSAKey key) {
          super(algName, alg);
          this.key = key;
     }
 
     @Override
     public byte sign(byte contentByte) throws IdpAppException {
 
          //*******************************************
          //RSAで署名を行う
          //*******************************************
          byte encryptedContent = null;
 
          if (key == null) {
               LOGGER.debug("秘密鍵が存在しない");
               return encryptedContent;
          }
 
          if (!(this.key instanceof PrivateKey)) {
               return encryptedContent;
          }
 
          try {
               Signature signature = Signature.getInstance(getAlg());
               signature.initSign((PrivateKey) this.key);
               signature.update(contentByte);
               encryptedContent = signature.sign();
          } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
               LOGGER.debug(e.getMessage(), e);
          }
 
          return encryptedContent;
     }
 
     @Override
     public boolean verify(byte contentByte, byte[] signatureByte) {
 
          //*******************************************
          //RSAで署名検証を行う
          //*******************************************
          boolean isValid = false;
 
          if (key == null) {
               LOGGER.debug("公開鍵が存在しない");
               return isValid;
          }
 
          if (!(this.key instanceof PublicKey)) {
               return isValid;
          }
 
          try {
               Signature signature = Signature.getInstance(getAlg());
               signature.initVerify((PublicKey) this.key);
               signature.update(contentByte);
               isValid = signature.verify(signatureByte);
          } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
               LOGGER.debug(e.getMessage(), e);
          }
 
          return isValid;
     }
}
 

 

5.最後に

RSAとHMACに関しては、すぐ情報が見つかって簡単であったがECDSAがなかなか良い情報がなくて苦労した。
最終的に断片的な知識の状態でこうすればいけるかな?という状態で作ったのでまともに動いたときは感動しましたわ(笑
それとJavapkcs形式でないと読み込めないため、opensslで作成した鍵を読み込ませるのには苦戦した。
特にECDSAの暗号化に関しては何かの参考になれば良いと思います。
 
以上