자신의 공개키를 인증 받기 위해 CA의 비밀키를 이용해서 만든 CA서명증서입니다.검증은 CA셀프인증서를 통해서 할 수 있습니다. 구체적으로는 CA(or Intermediate CA) 공개키로 위의 그림에서 서명알고리즘으로 복호화 한것과 서명이 일치하면 OK란거죠. 서명알고리즘에는 RSA계열과 ECDSA계열이 있습니다.
Go언어를 이용한 암호화 코딩 예제
0. HMAC 을 이용한 메세지 해싱
HMAC: hash message authentication code 즉 해싱에 의한 메세지 검증/인증으로 비밀키에 의해 -> 해싱은 되나 <- 반대 방향의 복구는 불가능한 , 즉 한방향으로만 작동하기 때문에 보통 확인 용도(보낸 내용이 무엇인지가 아니라, 보낸 내용이 보낼 때와 변경되진 않았는지 or 이미 저장되어 있는 정보와 일치하는지) 로 사용됩니다. 또한해싱의 특성상 원래의 것 보다 더 짧은 길이의 값이나 키로 변환해서 이득을 얻을 수 있는 많은 곳에 사용됩니다."crypto/hmac"
"crypto/sha256"
"crypto/sha512"
func main() {
message := "Today web engineering has modern apps ..."
salt := generateSalt()
fmt.Println("Message: " + message)
fmt.Println("\nSalt: " + salt)
hash := hmac.New(sha256.New, []byte(secretKey))
io.WriteString(hash, message+salt)
fmt.Printf("\nHMAC-Sha256: %x", hash.Sum(nil))
hash = hmac.New(sha512.New, []byte(secretKey))
io.WriteString(hash, message+salt)
fmt.Printf("\n\nHMAC-sha512: %x", hash.Sum(nil))
}
1. AES 대칭키를 이용한 암호화/복호화
서로 공유하고 있는 동일한 키(대칭키라고 함)로 암호화하고 복호화 하는 방식에 관한 코드입니다."crypto/aes"
func encrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
cfb := cipher.NewCFBEncrypter(block, iv)
...
}
func decrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
cfb := cipher.NewCFBDecrypter(block, iv)
...
}
2. RSA 개인키/공개키 생성
RSA 기반의 개인키와 공개키를 생성합니다. 대칭키와 다르게 2가지의 키가 생성됩니다.
rsaPriKey, rsaPubKey := GenerateKeyPair(1024) // 개인키와 공개키 쌍 생성
3. PEM EXPORT / IMPORT
개인키,공개키 혹은 인증서등을 pem 파일로 export/import 할 수 있습니다.여기서는 확장자를 pem으로 하였지만 crt (인증서) , key (개인키) 등으로 할 수도 있습니다.
func main(){
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
publicKey := &privateKey.PublicKey
pemPrivateFile, err := os.Create("private_key.pem")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var pemPrivateBlock = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
err = pem.Encode(pemPrivateFile, pemPrivateBlock)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
pemPrivateFile.Close()
privateKeyFile, err := os.Open("private_key.pem")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
pemfileinfo, _ := privateKeyFile.Stat()
size := pemfileinfo.Size()
pembytes := make([]byte, size)
buffer := bufio.NewReader(privateKeyFile)
_, err = buffer.Read(pembytes)
data, _ := pem.Decode([]byte(pembytes))
privateKeyFile.Close()
privateKeyImported, err := x509.ParsePKCS1PrivateKey(data.Bytes)
}
4. RSA 암호화/복호화
hello를 공개키로 암호화하고, 개인키로 복호화 합니다. (키쌍을 암호화에 사용)
rsaPriKey, rsaPubKey := GenerateKeyPair(1024)
encryptedMsg := EncryptWithPublicKey([] byte ("hello"), rsaPubKey2 ) decryptedMsg := DecryptWithPrivateKey(encryptedMsg, rsaPriKey2)
5. RSA 서명/확인
hello를 개인키로 서명하고, 공개키로 확인 합니다. (키 쌍을 서명을 위해 사용 + 자기부인 방지)
alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) alicePublicKey := alicePrivateKey.PublicKey
secretMessage := "Hello 8gwifi.org"
signature := SignPKCS1v15(secretMessage,*alicePrivateKey) verif := VerifyPKCS1v15(signature, secretMessage, alicePublicKey )
6. ECDSA 개인키/공개키 생성
하이퍼레즈 패브릭이나 이더리움에서는 RSA 말고 ECDSA를 사용합니다.
pubkeyCurve := elliptic.P256()
privatekey := new(ecdsa.PrivateKey)
privatekey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var pubkey ecdsa.PublicKey
pubkey = privatekey.PublicKey
7. ECDSA 서명/확인
RSA에서와 마찬가지로 서명/확인에 사용 할 수 있습니다.
pubkeyCurve := elliptic.P256()
privatekey := new(ecdsa.PrivateKey)
privatekey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
var pubkey ecdsa.PublicKey
pubkey = privatekey.PublicKey
var h hash.Hash
h = md5.New()
r := big.NewInt(0)
s := big.NewInt(0)
io.WriteString(h, "This is a message to be signed and verified by ECDSA!")
signhash := h.Sum(nil)
r, s, serr := ecdsa.Sign(rand.Reader, privatekey, signhash)
signature := r.Bytes()
signature = append(signature, s.Bytes()...)
verifystatus := ecdsa.Verify(&pubkey, signhash, r, s)
fmt.Println(verifystatus)
8. ECDH 로 (비밀키 전송없이) 비밀키 공유하기
간단히 말해서 나의 비밀키 * 상대방의 공개키 == 상대방의 비밀키 * 나의 공개키 라는 등식을 이용해서, 공통의 비밀키를 갖는 방식입니다. 이더리움에서는 피어끼리 접속 할 때마다 임시의 공개키/개인키를 만들어서 (참고로 임시가 아닌 static 공개키는 바로 자신의 주소죠) ECDH를 이용하여 만든 비밀키로 RLP인코딩한 패킷을 암호화하여 송/수신합니다. var privKey1, privKey2 crypto.PrivateKey
var pubKey1, pubKey2 crypto.PublicKey
var err error
var ok bool
var secret1, secret2 []byte
privKey1, pubKey1, err = e.GenerateKey(rand.Reader)
privKey2, pubKey2, err = e.GenerateKey(rand.Reader)
secret1, err = e.GenerateSharedSecret(privKey1, pubKey2)
secret2, err = e.GenerateSharedSecret(privKey2, pubKey1)
if !bytes.Equal(secret1, secret2) {
log.Print("The two shared keys: %d, %d do not match", secret1, secret2)
}
9. PKI 시스템 (CA생성,서명하기,서명 확인하기)
웹서비스 회사에서 https 서비스를 하기 위해 Verisign, Comodo, Thawte, GeoTrust, GlobalSign 같은 CA로 부터 자신의 공개키를 그들의 개인키로 서명한 인증서(. X.509)를 받아서 사용합니다. 이렇게 CA를 통하여 인증을 받은 공개키를 통해 신원을 검증하고 비밀키 교환을 하는 일련의 과정에 인프라를 PKI라고 합니다.
하이퍼레저 패브릭 같은 콘소시엄형 블록체인은 각 조직에 대한 허가/인증이 중요한 기능이기에 이런 PKI를 시스템내에 직접 구현하여 활용합니다. 먼가 복잡해 보이지만 개인키,공개키 만드는 로직/인증서 만드는 로직/ 인증서 검증하는 로직만 있으면 만들 수 있습니다.
- CA 자가서명 (CA는 루트이므로 자기 스스로 서명하게 됩니다.)func main() {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) publicKey := &privateKey.PublicKey
ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, publicKey, privateKey)
if err != nil {
log.Println("create ca failed", err)
return
}
certOut, err := os.Create("ca.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: ca_b})
certOut.Close()
log.Print("written cert.pem\n")
keyOut, err := os.OpenFile("ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
x509Encoded, _ := x509.MarshalECPrivateKey(privateKey)
pem.Encode(keyOut, &pem.Block{Type: "ECDSA PRIVATE KEY", Bytes: x509Encoded})
keyOut.Close()
log.Print("written key.pem\n")
}
- CA를 이용한 개인 인증서 생성 및 확인 (개인들은 자신의 공개키/개인키를 만든 후에 공개키를 CA의 개인키를 이용하여 서명하고 X.509구조의 인증서를 만들게 됩니다)
package main
import (
"bufio"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
func main() {
catls, err := tls.LoadX509KeyPair("ca.crt", "ca.key")
if err != nil {
panic(err)
}
ca, err := x509.ParseCertificate(catls.Certificate[0])
if err != nil {
panic(err)
}
cert := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Organization: []string{"ORGANIZATION_NAME"},
Country: []string{"COUNTRY_CODE"},
Province: []string{"PROVINCE"},
Locality: []string{"CITY"},
StreetAddress: []string{"ADDRESS"},
PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) publicKey := &privateKey.PublicKey
cert_b, err := x509.CreateCertificate(rand.Reader, ca, ca, publicKey, privateKey)
if err != nil {
log.Println("create ca failed", err)
return
}
certOut, err := os.Create("bob.crt")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert_b})
certOut.Close()
log.Print("written cert.pem\n")
keyOut, err := os.OpenFile("bob.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
x509Encoded, _ := x509.MarshalECPrivateKey(privateKey)
pem.Encode(keyOut, &pem.Block{Type: "ECDSA PRIVATE KEY", Bytes: x509Encoded})
keyOut.Close()
log.Print("written key.pem\n")
cert, _ = x509.ParseCertificate(cert_b)
ecdsaPublicKey := cert.PublicKey.(*ecdsa.PublicKey)
log.Print(ecdsaPublicKey)
certFile, err := os.Open("bob.crt")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
pemfileinfo, _ := certFile.Stat()
size := pemfileinfo.Size()
pembytes := make([]byte, size)
buffer := bufio.NewReader(certFile)
_, err = buffer.Read(pembytes)
data, _ := pem.Decode([]byte(pembytes))
certFile.Close()
cert2, _ := x509.ParseCertificate(data.Bytes)
ecdsaPublicKey2 := cert2.PublicKey.(*ecdsa.PublicKey)
log.Print(ecdsaPublicKey2)
checkSign:= cert2.CheckSignatureFrom(ca)
if checkSign == nil {
log.Print("check ok")
}
}
위 예제들의 완전한 코드는 이런 예제를 이용하여 하이퍼레저 패브릭 MSP를 시뮬레이션 해보는 작업과 함께 다음 글에서 MSP 코드와 함께 Github에 올릴 예정입니다.
다음 글: 400라인의 go코드로 구현한 하이퍼레저 패브릭 [4-1]- MSP (2) (작성중..)
레퍼런스:
https://fale.io/blog/2017/06/05/create-a-pki-in-golang/
https://8gwifi.org/docs/go-rsa.jsp
https://github.com/hyperledger/fabric/blob/release-1.4/common/tools/cryptogen/main.go
https://github.com/ethereum/go-ethereum/tree/master/p2p
https://github.com/hyperledger/fabric/tree/release-1.4/msp