blog

暗号技術 - 非対称暗号化

この関数は、ランダムデータ生成器 random を用いて、指定されたワードビット数の RSA 鍵のペアを生成します。 公開鍵は、x509 を介して PKIX 形式の DER エンコーディングにシリアラ...

Sep 23, 2020 · 16 min. read
シェア
"非対称暗号は公開鍵暗号とも呼ばれる。: 公開鍵で暗号化し、秘密鍵で復号する "

共通鍵暗号では、暗号化と復号化の鍵が同じであるため、鍵を受信者に配布する必要があります。復号に使用される鍵は受信者に配布されなければならず、この問題は鍵配布問題として知られています。公開鍵暗号とも呼ばれる非対称暗号を用いれば、復号に用いる鍵を受信者に配布する必要がないため、鍵の配布問題は解決されます。非対称暗号は暗号史上最大の発明と言えます。

非対称暗号化では、鍵は暗号化鍵と復号鍵の2種類に分けられます。送信者は暗号鍵でメッセージを暗号化し、受信者は復号鍵で暗号文を復号します。公開鍵暗号方式を理解するには、暗号化鍵と復号鍵を明確に区別することが非常に重要です。暗号化キーは送信者が暗号化に使用し、復号化キーは受信者が復号化に使用します。

暗号化キーと復号化キーの違いをよく考えてください:

  • 送信者が必要とするのは暗号化キーだけです。
  • 受信者が復号化する必要があるのは鍵だけです。
  • 復号化キーには盗聴者はアクセスできません。
  • 暗号化キーに盗聴者がアクセスしても大丈夫です。

つまり、復号鍵は最初から受信者が持っているので、暗号鍵を送信者に送るだけで鍵配布の問題は解決でき、復号鍵を配布する必要は全くありません。

非対称暗号化では、暗号鍵は一般に公開されます。暗号鍵が公開鍵と呼ばれるのは、暗号鍵を自由に公開できるからです。公開鍵は、電子メールで受信者に直接送ったり、新聞広告に掲載したり、看板にして街頭に置いたり、ウェブページにして世界中の誰もが盗聴される心配なく利用できるようにしたりすることができます。

もちろん、世界中のすべての人に公開鍵を公開する必要はありませんが、少なくとも、公開鍵を使って暗号化された通信が必要な相手には、公開鍵を送る必要があります。

対照的に、復号鍵は決して公開されることはなく、この鍵は自分だけが使うことができるため、秘密鍵と呼ばれています。秘密鍵は他の誰にも知られることはありませんし、通信相手にも送ることはできません。

公開鍵と秘密鍵は1対1の対応関係にあり、公開鍵と秘密鍵のペアを総称して鍵ペアと呼びます。公開鍵で暗号化された暗号文は、公開鍵とペアになった秘密鍵で復号化されなければなりません。鍵ペアの2つの鍵は互いに非常に密接な関係にあるため、公開鍵と秘密鍵を別々に生成することはできません。

公開鍵暗号の利用者は、公開鍵と秘密鍵を含む鍵ペアを生成する必要があります。鍵ペアを生成する具体的な試みは後述します。

非対称暗号化通信フロー

公開鍵暗号を使った通信の流れを見てみましょう。前回同様、アリスが送信者、ボブが受信者、そして今回も盗聴者イブが通信内容を盗聴できると仮定して、アリスがボブにメッセージを送るとします。

公開非対称暗号通信では、通信プロセスは受信者のBobによって開始されます。

  1. Bobは公開鍵と秘密鍵を含む鍵ペアを生成します。

    秘密鍵はボブ自身が保管しています。

  2. ボブは自分の公開鍵をアリスアに送ります。

    Bobの公開鍵が盗聴者のEveに傍受されても問題ありません。

    公開鍵をアリスに送るということは、ボブはアリスにこの公開鍵でメッセージを暗号化して送るように頼むということです。

  3. アリスはボブの公開鍵でメッセージを暗号化します。

    暗号化されたメッセージは、ボブの秘密鍵によってのみ復号化することができます。

    アリスはボブの公開鍵を持っていますが、ボブの公開鍵で暗号文を復号することはできません。

  4. アリスはボボに暗号文を送信。

    Bobの公開鍵を持っていても、Bobの公開鍵で復号できない盗聴者Eveによって暗号文が傍受されても問題ありません。

  5. Bobは自分の秘密鍵で暗号文を復号します。

盗聴者のEveはBobの公開鍵を持っているかもしれませんが、Bobの公開鍵は暗号化鍵であって復号鍵ではないので、盗聴者のEveは復号操作を完了することができません。

RSA

非対称暗号の鍵は暗号化鍵と復号鍵に分かれますが、具体的にはどのように行われるのでしょうか。ここでは、最も広く使われている公開鍵暗号アルゴリズムであるRSAについて説明します。

RSAは非対称暗号化アルゴリズムで、その名前は3人の開発者の姓の頭文字、RonRivest、AdiShamir、LeonardAdlemanから成っています。

RSAは非対称暗号化とデジタル署名に使えますが、これについては後の章で説明します。

1983年、RSA社は米国でRSAアルゴリズムに関する特許を取得しましたが、この特許は現在失効しています。

RSA

ここでようやく、非対称暗号の代表であるRSAの暗号化処理についてお話することができます。RSAの暗号化処理は以下の式で表せます。

密文=EmodNRSA加密)暗号=平文EmodN(RSA暗号化)

言い換えれば、RSA 暗号は modN に平文を表す数値の Eth 乗を掛けた結果です。言い換えれば、平文を E 倍し、その結果を N で割った余りが暗号文となります。

え、それだけ?

ええ、とても簡単です。平文を掛け算してmodを解くだけで、暗号化できるのです。スクランブルエッグを作るようにビット列を移動させたり、XOR(ビットによる微分)のような操作を正しく行うなど、複雑な関数や操作がたくさんある対称暗号とは異なり、RSAは非常にシンプルです。

ところで、暗号化式に登場する2つの数字、EとNは1つずつですが、これらは一体何なのでしょうか。RSAの暗号化は、平文のEth乗modNを求めるものですから、EとNという数字さえ知っていれば、誰でも暗号化操作を完了することができます。つまり、EとNがRSA暗号の鍵、つまりEとNの組み合わせが公開鍵です。

しかし、EとNはただの数字ではなく、厳密な計算から導き出されたものです。ちなみにEは暗号の頭文字、Nは数字の頭文字です。

非常に誤解を招きやすい点ですが、EとNという数字は鍵ペアではありません。公開鍵を構成するのはEとNという数字なので、通常は「公開鍵は」または「公開鍵は{E, N}」という形で、EとNという数字を括弧で囲んで書きます。

RSA暗号が「modNのEth乗を求める」==であることはもうお分かりでしょう。

RSA

RSAの復号化は暗号化と同様に簡単で、以下の式で表すことができます:

明文=DmodNRSA解密)平文=暗号文DmodN(RSA復号)

言い換えれば、平文は暗号文を表す数の D 乗の modN を解くことで得られます。言い換えれば、暗号文を D 乗し、その結果を N で割って余りを求めれば平文が得られます。

ここで使われる数Nは、暗号化に使われる数Nと同じです。数字Dと数字Nの組み合わせがRSAの復号鍵ですから、DとNの組み合わせが秘密鍵です。DとNの数字を知っている人だけが復号操作を行うことができます。

RSAでは、暗号化と復号化が同じ形式であることにお気づきでしょう。暗号化は「mod NのE乗」を要求し、復号化は「mod NのD乗」を要求しています。

もちろん、復号鍵Dと数Eはかなり密接な関係があるので、Dはただの数ではありません。そうでなければ、Eで暗号化した結果をDで復号することはできません。

ちなみにDはDecryptionの頭文字、Nは数字の頭文字です。

以上の話を整理すると、下の表のようになります。

 **RSAの暗号化と復号化**

Go での公開鍵と秘密鍵の生成

導入予定パッケージ

import (
	"crypto/rsa"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"os"
)

秘密鍵の生成操作フローの概要

  1. 秘密鍵を生成するには、rsaのGenerateKeyメソッドを使用します。
  2. 取得したras秘密鍵を、x509標準によってASN.1 DERエンコードされた文字列にシリアライズします。
  3. 秘密鍵の文字列をpem形式のブロックに設定します。
  4. セットデータをpemでエンコードし、ディスクファイルに書き込みます。

公開鍵生成の操作フロー

  1. 取得した秘密鍵オブジェクトから公開鍵情報を削除します。
  2. 取得したrsa公開鍵を、x509標準を使って文字列にシリアライズします。
  3. 公開鍵文字列をpem形式ブロックに設定します。
  4. セットデータをpemでエンコードし、ディスクファイルに書き込みます。

公開鍵と秘密鍵を生成するためのソースコード

// パラメータ・ビット: 生成される秘密鍵の長さを、以下の単位で指定する。: bit
func RsaGenKey(bits int) error{
	// 1. 秘密鍵ファイルを生成する
	// GenerateKeyこの関数は、ランダムデータ生成器 random を用いて、指定されたワードビット数の RSA 鍵のペアを生成する。
	//  : Readerは暗号用のグローバルな共有強力乱数生成器である。
	//  : 秘密鍵のビット数 - bit
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil{
		return err
	}
	// 2. MarshalPKCS1PrivateKeyrsa秘密鍵をASNにシリアライズする.1 PKCS#1 DER 
	derStream := x509.MarshalPKCS1PrivateKey(privateKey)
	// 3. BlockPEMコードの構造を表し、それを設定する。
	block := pem.Block{
		Type: "RSA PRIVATE KEY",//"RSA PRIVATE KEY",
		Bytes: derStream,
	}
	// 4. ファイルを作成する
	privFile, err := os.Create("private.pem")
	if err != nil{
		return err
	}
	// 5. pemエンコーディングを使用し、データをファイルに書き込む。
	err = pem.Encode(privFile, &block)
	if err != nil{
		return err
	}
	// 6. 最後にファイルを閉じる
	defer privFile.Close()
	// 7. 公開鍵ファイルを生成する
	publicKey := privateKey.PublicKey
	derPkix, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil{
		return err
	}
	block = pem.Block{
		Type: "RSA PUBLIC KEY",//"PUBLIC KEY",
		Bytes: derPkix,
	}
	pubFile, err := os.Create("public.pem")
	if err != nil{
		return err
	}
	// 8. 暗号化された公開鍵、書き込みファイル
	err = pem.Encode(pubFile, &block)
	if err != nil{
		panic(err)
		return err
	}
	defer pubFile.Close()
	return nil
}

重要な機能が紹介されています。

  1. GenerateKey関数は、ランダムデータ生成器randomを使用して、指定されたワードビット数のRSA鍵のペアを生成します。

    "crypto/rsa" パッケージの機能
    func GenerateKey(random io.Reader, bits int) (priv *PrivateKey, err error)
     -  : io.Reader:  : rand.Reader
     -- randパッケージは暗号化と復号化により安全な乱数生成器を実装している。
     -- var Reader io.Reader (randパッケージ内の変数)
     -  : bits: 秘密鍵の長さ
     - 戻り値 1: RSA秘密鍵を表す。
     - 戻り値2: エラー・メッセージ
    
  2. rsa 秘密鍵を ASN.1 PKCS#1 DER エンコーディングに x509 経由でシリアライズします。

    "crypto/x509" パッケージ内の関数(x509パッケージはXを解析する.509暗号化された証明書と鍵)。
    func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
     -  : rsaによって.GenerateKey得られた秘密鍵
     -  : 秘密鍵をASNに通す.1シリアル化後に得られる秘密鍵暗号化データ
    
  3. Pemコーディング構造の設定

    BlockはPEMエンコーディングの構造を表している。
    type Block struct {
     Type string // プリアンブルに由来するタイプ
     Headers map[string]string // オプションのヘッダー項目、Headersは複数行のキーと値のペアで、空でもよい。
     Bytes []byte // コンテンツ復号後のデータ、一般的にはDERエンコードされたASN.1 
    }
    
  4. 出来上がったPem形式の秘密鍵を、ファイルポインタを介してディスクに書き込みます。

    "encoding/pem" パッケージの機能
    func Encode(out io.Writer, b *Block) error
     -  : 書き込み可能なIOオブジェクトで、ファイル・ポインタを指定する必要がある。
     -  : 初期化されたPemブロック・オブジェクト、すなわちブロック・オブジェクト
    
  5. RSA秘密鍵による公開鍵

    //  
    type PrivateKey struct {
     PublicKey //  
     D *big.Int // プライベートのインデックス
     Primes []*big.Int // N少なくとも2つの
     // 事前に計算された値が含まれており、場合によっては秘密鍵の運用を高速化できる。
     Precomputed PrecomputedValues
    }
    //  
    type PublicKey struct {
     N *big.Int //  
     E int // 公開されているインデックス
    }
    秘密鍵を通して公開鍵を得る
    publicKey := privateKey.PublicKey // privateKey秘密鍵オブジェクトの場合
    
  6. 公開鍵をx509経由でPKIX形式のDERエンコーディングにシリアライズ。

    "crypto/x509" パッケージの機能
    func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
     -  : 秘密鍵オブジェクトを通して得られる公開鍵
     - 戻り値1:公開鍵をASNに渡す。.1シリアル化後に得られる暗号化データ
     - 戻り値 2: エラー・メッセージ
    
  7. 出来上がったPem形式の公開鍵を、ファイル・ポインタを介してディスクに書き込みます。

  8. 生成された秘密鍵と公開鍵のファイル・データ

    // 秘密鍵ファイルのデータ
    -----BEGIN RSA PRIVATE KEY-----
    MIICXgIBAAKBgQC5bm0DCEV+EFeiLUqSshziqhSB30jXy5BWbPV5SlMq4aWiEknM
    i+Mw1aXic4bEsM3YyT73eWsifqZNSc/4fRaV4qz5OL8IIe9AZoGDSLX/Ar9AQMJf
    OHbAtdIlCGQ4d80KjpDpPs2wZkTqllWCgDVEm5kqTGtSYIu9e7JQIDAQAB
    AoGARGdn72ZtvENrEHiEufjajwMO7Zng1TpS1I79PvEcHQWAhHkaoEo6VRl7SD41
    yPkv9njGsaQo0WDHGFvSTGhYm/EWGrBWRPc5xXbSBg7ty9Iza9B1ekAj8VfWryen
    Wje3xDOCVCDUiCcYdaSfPiJPYuWMSnNMNa+0cR921zBQg0ECQQDpCMljuH7LrpbC
    NDF5q+LbUWMAE2KLDPX4WmDSdZdIO3mPux3MdwOUEfrcvSBGZNB7gyaEG7goZL8G
    BqL22MJHAkEAy7SqbVPoPbMPHuLI52VQ2FDp6xxSWLhjmv1ePCHGo28MDCaHeVzZ
    QaxyuIbnY8A6NHfu/QGwz/eB941IjYNBMwJBAI9XEEl+mr++zIz4fdZRnGE7VqId
    SmgtuL7jGNtb6YpMyyFV/6ZdLp5N0PkmfEvQh0zyBycLxeNS1Q1n16Xu/tECQQCZ
    dF42wdDgOfWYFMu31VETw9CTtuApya3vYhMNRXx4Pf1bYeMIf/OCT8CUVbwWHwc5
    42d73TwvTorvy9TuFgSVAkEA6F69THlTn5oIP8IWHcHuqS01fIR/vGfEwQ4cFZGR
    ketfieyeeF8rjn4qzwT/ugwRNjkhfKmoILnIC8UhEEJdjA==
    -----END RSA PRIVATE KEY-----
    
    // 公開鍵ファイル・データ
    -----BEGIN RSA PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5bm0DCEV+EFeiLUqSshziqhSB
    30jXy5BWbPV5SlMq4aWiEknMi+Mw1aXic4bEsM3YyT73eWsifqZNSc/4fRaV4qz5
    OL8IIe9AZoGDSLX/Ar9AQMJfOHbAtdIlCGQ4d80KjpDpPs2wZkTqllWCgD
    VEm5kqTGtSYIu9e7JQIDAQAB
    -----END RSA PUBLIC KEY-----
    

GoにおけるRSAの使用

  1. 手続き

    • 公開鍵暗号

      1. 公開鍵ファイルから公開鍵を読み出し、pemでエンコードされた文字列を取得します。
      2. 結果の文字列をデコード
      3. x509を使ったエンコードされた公開鍵の解析
      4. 取得した公開鍵を用いたrsaによるデータ暗号化
    • 秘密鍵の復号

      1. 秘密鍵ファイルから秘密鍵を読み出し、pemエンコードされた文字列を取得します。
      2. 結果の文字列をデコード
      3. x509を使用したエンコードされた秘密鍵の解析
      4. 取得した秘密鍵を用いたrsaによるデータ復号化
  2. コードの実装

    • RSA公開鍵暗号化

      func RSAEncrypt(src, filename []byte) []byte {
       // 1. ファイル名に基づいてファイルの内容を読み出す
       file, err := os.Open(string(filename))
       if err != nil {
       return nil
       }
       // 2.  
       info, _ := file.Stat()
       allText := make([]byte, info.Size())
       file.Read(allText)
       // 3. ファイルを閉じる
       file.Close()
       // 4. データから次のPEMフォーマットブロックを見つける
       block, _ := pem.Decode(allText)
       if block == nil {
       return nil
       }
       // 5. DERエンコードされた公開鍵を解析する
       pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
       if err != nil {
       return nil
       }
       pubKey := pubInterface.(*rsa.PublicKey)
       // 6. 公開鍵暗号化
       result, _ := rsa.EncryptPKCS1v15(rand.Reader, pubKey, src)
       return result
      }
      
    • RSA秘密鍵の復号化

      func RSADecrypt(src, filename []byte) []byte {
       // 1. ファイル名に基づいてファイルの内容を読み出す
       file, err := os.Open(string(filename))
       if err != nil {
       return nil
       }
       // 2.  
       info, _ := file.Stat()
       allText := make([]byte, info.Size())
       file.Read(allText)
       // 3. ファイルを閉じる
       file.Close()
       // 4. データから次のPEMフォーマットブロックを見つける
       block, _ := pem.Decode(allText)
       // 5. pem形式の秘密鍵を解析する
       privateKey , err := x509.ParsePKCS1PrivateKey(block.Bytes)
       // 6. 秘密鍵の復号化
       result, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, src)
       return result
       }
      
    • 重要機能の紹介

      1. 出来上がったPem形式の秘密鍵を、ファイルポインタを介してディスクに書き込みます。

        "encoding/pem" パッケージの機能
        func Decode(data []byte) (p *Block, rest []byte)
         - パラメータ・データ: 解析されるデータブロック
         - 戻り値 1: パラメータから解析されたPEMフォーマットのブロック
         - 戻り値2: パラメータ・データ 残りの復号化されていないデータ
        
      2. DERエンコードされた公開鍵をパースします。pemのブロック構造のデータ形式はASN.1エンコードです。

        この関数が属するパッケージ: "crypto/x509"
        func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error)
         - パラメータ derBytes: pemのブロック構造から引用したASN.1暗号化されたデータ
         - 戻り値 pub: インターフェース・オブジェクト、実際には公開鍵データ
         - 引数 err: エラー・メッセージ
        
      3. DERエンコードされた秘密鍵のパース、pemのブロック構造のデータ形式はASN.1エンコードされています

        この関数が属するパッケージ: "crypto/x509"
        func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err error)
         - パラメータ: pemのブロック構造から引用したASN.1暗号化されたデータ
         - 返り値 キー: 解析された秘密鍵
         - 戻り値 err: エラー・メッセージ
        
      4. インターフェースの公開鍵への変換

        pubKey := pubInterface.(*rsa.PublicKey)
         - pubInterface: ParsePKIXPublicKey関数{}  
         - pubInterface.(*rsa.PublicKey): pubInterfaceを公開鍵タイプrsaに変換する。.PublicKey
        
      5. 公開鍵によるデータの暗号化

        この関数が属するパッケージ: "crypto/rsa"
        func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error)
         - 引数 rand: 乱数生成器、randに割り当てられる.Reader
         - パラメータ・パブ: 非対称暗号化の暗号化には公開鍵が使われる。
         - パラメータ msg: 公開鍵を使用して暗号化される生データ
         - 戻り値 out: 暗号化後のデータ
         - 戻り値 err: エラー・メッセージ
        
      6. 秘密鍵によるデータの復号化

        この関数が属するパッケージ: "crypto/rsa"
        func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error)
         - 引数 rand: 乱数生成器、randに割り当てられる.Reader
         - パラメータ priv: 非対称暗号化と復号化に使われる秘密鍵
         - 引数 ciphertext: 秘密鍵を使って復号化するデータ
         - 戻り値 out: 復号後に得られるデータ
         - 戻り値 err:  
        

ECC楕円曲線

  1. 概念的

    楕円曲線暗号は、公開鍵暗号を確立するためのアルゴリズムで、楕円曲線数学に基づいています。暗号における楕円曲線の使用は、1985年にNeal KoblitzとVictor Millerによってそれぞれ独自に提案されました。

    ECCの主な利点は、場合によっては、RSA暗号化アルゴリズムなど、より小さな鍵を使用する他の方法と同等かそれ以上のレベルのセキュリティを提供できることです。

    楕円曲線暗号には若干異なる形式がありますが、いずれも楕円曲線離散対数問題を解くことの難しさが広く認識されていることに依存しています。大きな素数の因数分解の難しさに基づく従来の暗号手法とは異なり、ECCは楕円曲線方程式の性質によって鍵を生成します。

  2. 数学の原理

    RSAであれECCであれ何であれ、公開鍵暗号化アルゴリズムは、前方への計算が容易で後方への計算が困難な数学的問題に依存しています。

    楕円曲線が依存する数学的難問とは。

    kは正の整数、Pは楕円曲線上の点である。, k*P=Q , QとPがわかれば、kを計算するのは難しい。

非対称暗号の謎解き

  • 非対称暗号は対称暗号よりも機密性が高いのですか?

    鍵の長さによって機密性のレベルが異なるため、この質問にはお答えできません。

  • 秘密鍵長1024bitの非対称暗号と秘密鍵長128bitの対称暗号では、秘密鍵長の長い非対称暗号の方が安全ですか?

    違います。

    非対称暗号の鍵長は、対称暗号の鍵長と直接比較することはできません。次の表は鍵長の比較表で、これによると、1024 ビットの公開鍵暗号は 128 ビットの共通鍵暗号よりもブルートフォース解読に強いことがわかります。

    1282304
    1121792
    80768
    64512
    56384
  • 非対称暗号化では、将来的に対称暗号化は置き換えられるのでしょうか?

    それはない

    一般に、非対称暗号は、同じレベルの機密性を持つ鍵長を使用した場合、対称暗号の数百分の一の処理速度しかありません。そのため、非対称暗号化は非常に長いメッセージ内容の暗号化には適していません。目的によっては、対称暗号と非対称暗号を併用することもあります。例えば、ハイブリッド暗号はその両方を組み合わせたものです。

Read next

J24 DOMの動的操作

1. DOM要素の追加/削除/変更 2. 要素のカスタム属性の設定

Sep 23, 2020 · 3 min read