E2EE暗号化の仕組み
はじめに
E2EE暗号化が普及した背景には、疑似乱数生成アルゴリズムDual_EC_DRBGにバックドアが存在する可能性がある疑惑や、エドワード・スノーデン氏が内部告発したPRISMによる監視・盗聴行為などが明らかになり、高度な暗号化や安全性の高い暗号化技術が求められるようになりました。
E2EEとは
E2EE(End-to-end encryption)とは、利用者のみが鍵を持ち、端末間で暗号化されているためMITM攻撃などによる盗聴の対策になります。
前方秘匿性(FS)や楕円曲線上の離散対数問題の困難さから、安全性の高いことが特徴です。
第三者による解読を困難にするため事実上、利用者のみデータを復号出来ます。
一方で、犯罪捜査や違法コンテンツなどの対策が困難になることが問題視されてる。(サービス提供者も復号出来ないため)
E2EEが利用されてるサービス
- ProtonMail
- Wire
- LINE
- Telegarm
- Zoom
- Signal
など
仕組み
1. 鍵ペア生成
クライアント側でprivateKey
とpublicKey
を生成します。
本記事ではcurve25519^1を用いて鍵ペアを生成しています。
コード
type keyPair struct {
priKey *[32]byte
pubKey *[32]byte
}
func generateKey() *keyPair {
privateKey, publicKey := new([32]byte), new([32]byte)
if _, err := rand.Read(privateKey[:]); err != nil {
log.Fatal(err)
}
curve25519.ScalarBaseMult(publicKey, privateKey)
return &keyPair{
priKey: privateKey,
pubKey: publicKey,
}
}
2. shared secret(共有秘密鍵)生成
1で生成した鍵ペアのprivateKey
と相手のpublicKey
を使い生成します。
shared secret
が両者で異なれば暗号化された内容は復号出来ない。
コード
type keyPair struct {
priKey *[32]byte
pubKey *[32]byte
}
func (k *keyPair) KeyExcahenge(publicKey []byte) ([]byte, error) {
return curve25519.X25519(k.priKey[:], publicKey)
}
3. 暗号化/復号化
AES-256-GCM
で生成したshared secret
を用いて暗号化する。
AAD(additional authenticated data)などは各自で実装してください。
コード
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func randomBytes(size int) []byte {
dst := make([]byte, size)
if _, err := rand.Read(dst); err != nil {
panic(err.Error())
}
return dst
}
func encrptKey(key []byte) []byte {
salt := randomBytes(16)
sha := sha256.New()
sha.Write(salt)
sha.Write(key)
return sha.Sum(nil)
}
func encrypt() {
// この鍵は生成した共有秘密鍵を使う
key, _ := hex.DecodeString("4b062aa58974ab41900050afd7ad35cbb33342b10d499737ef011c44c95df218")
encKey := encrptKey(key)
plaintext := []byte("hello, world!")
block, err := aes.NewCipher(encKey)
if err != nil {
panic(err.Error())
}
nonce := randomBytes(12)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil)
// nonce(ciphertext[:12]) + data(ciphertext[12:])
fmt.Printf("EncryptedData: %x\n", ciphertext)
}
func main() {
encrypt()
}
追記:
鍵ペアを生成した後、privateKey
をデバイスに安全に保管しpublicKey
を登録用のAPIや公開鍵をやり取りする機能に登録する。
鍵登録時に鍵に、一意な識別子をバインドすることでより改竄に対し強固になる。
本記事ではAES-256-GCM
で暗号化しました、認証付き暗号のため通信回路上で改竄された場合に検知することが出来ます。
Go1.20で導入されたcrypto/ecdh
でX25519が使えるようになったので、golang.org/x/crypto/curve25519
を置き換えることができます。
↓コードはこちらから見ることが出来ます
【鍵ペア~共有秘密鍵生成】
【暗号化】
終わりに
本記事ではGolangでの実装になりますが、他の言語でも利用することが出来るためE2EE暗号化に興味のある方はぜひ試してみてください。
内容に誤りや不備を発見してくれた方は、指摘くださると大変助かります。
Discussion