타원곡선 DSA - 위키백과, 우리 모두의 백과사전

ecdsa 는 타원 곡선 DSA (Elliptic Curve Digital Signature Algorithm) 입니다.

타원곡선을 사용한 비 대칭키 전자서명 알고리즘 입니다.

타원곡선 키 페어

다음 함수로 타원곡선 알고리즘의 공개/비밀키를 생성합니다.

priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// 타원, 랜덤 리더

타원은 elliptic.P224(),  elliptic.P256(), elliptic.P384() elliptic.P521() 가 있습니다.

서명

다음 함수로 메세지를 서명합니다.

r, s, err := ecdsa.Sign(rand.Reader, priv, data)

r, s 는 서명 값입니다.

바이트 슬라이스로 얻고자 하면, 다음 함수를 사용합니다.

sign, err := ecdsa.SignASN1(rand.Reader, priv, data)

검증

다음 함수로 사인을 검증합니다.

ok := ecdsa.Verify(&priv.PublicKey, msg, r, s)
ok := ecdsa.VerifyASN1(&priv.PublicKey, msg, sign)

올바른 서명을 입력하면, true 가 리턴 됩니다.

인증서 인코딩

Go cypto - 4. RSA 키 인코딩/디코딩
rsa 키를 네트워크, 파일 등으로 작성하는 경우도 종종 있습니다. 이러한 경우, rsa 키를 인/디코딩 하는 표준이 존재합니다. x509 패키지에서 PKCS#1 PKCS#8 PKIX 구현체를 사용하여 rsa 키를 바이트 슬라이스로 파싱해 보겠습니다. 그리고 이 데이터를 PEM 으로 인/디코딩 해보겠습니다. 비밀키마샬OpenSSL 등에서 rsa 키는 PKCS#1 으로 비밀키로 출력합니다.

rsa 처럼, 공개키는 x509.MarshalPKIXPublicKey(), x509.ParsePKIXPublicKey()
비밀키는
x509.MarshalECPrivateKey(), x509.ParseECPrivateKey() 또는,
x509.MarshalPKCS8PrivateKey(), x509.MarshalPKCS8PrivateKey() 를 선택해서 사용합니다.

OpenSSL 등이 출력하는 인증서는 비밀키 마샬/파싱을
x509.MarshalECPrivatekey(), x509.ParseECPrivatekey()
을 사용해서 사용하면 됩니다.

pem 출력, 파싱

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"fmt"
)

func main() {
	priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

	privDer, _ := x509.MarshalECPrivateKey(priv)
	pubDer, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey)

	privBlock := &pem.Block{
		Type:  "ECDSA PRIVATE KEY",
		Bytes: privDer,
	}

	pubBlock := &pem.Block{
		Type:  "ECDSA PUBLIC KEY",
		Bytes: pubDer,
	}

	privPEM := pem.EncodeToMemory(privBlock)
	pubPEM := pem.EncodeToMemory(pubBlock)

	fmt.Printf("PRIVATE\n%s\n\n", privPEM)
	fmt.Printf("PUBLIC\n%s\n", pubPEM)
}
// 출력
PRIVATE
-----BEGIN ECDSA PRIVATE KEY-----
MHcCAQEEILClafUZ8QAOQTRwCbZKjlL4l7T9z+zY2GqKgFepqVkGoAoGCCqGSM49
AwEHoUQDQgAEKKF+2h7j9gU883kLFcy+CYb7qJQF0Om2ALFDFvngZgsJGSEdkmFy
HZyqIgkJKYZj2uTnUsafs4N0iKullFmJ9Q==
-----END ECDSA PRIVATE KEY-----


PUBLIC
-----BEGIN ECDSA PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKKF+2h7j9gU883kLFcy+CYb7qJQF
0Om2ALFDFvngZgsJGSEdkmFyHZyqIgkJKYZj2uTnUsafs4N0iKullFmJ9Q==
-----END ECDSA PUBLIC KEY-----

예시

파일 서명

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"os"
)

var Path = "/home/code/LICENSE"

func main() {
	f, _ := os.Open(Path)

	hasher := sha256.New()

	io.Copy(hasher, f)

	fileHash := hasher.Sum(nil)

	priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

	sign, _ := ecdsa.SignASN1(rand.Reader, priv, fileHash)

	pubDer, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey)
	pubPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "ECDSA PUBLIC KEY",
		Bytes: pubDer,
	})

	fmt.Printf("file %s signiture:\n%x\n", Path, sign)
	fmt.Printf("PUBLIC KEY:\n%s\n", pubPEM)
}
// 출력
file /home/code/LICENSE signiture:
304402200cf3fbba969cad28b904c33baa0c1dd973bd6d11b79b22f070a5602156405a0b0220371400e9446c3ddc13eae286ed36e793ba6505e375f63a0eb3d18342c52452bf
PUBLIC KEY:
-----BEGIN ECDSA PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMWv37ewL+vK1ynI2qQqX3QEtEcnG
5iKhrBxtkmYJgXhtaBDSNZikXu9045E5EJBPFfWolaWHFKIN1A0h/xY6Cg==
-----END ECDSA PUBLIC KEY-----

서명 검증

package main

import (
	"crypto/ecdsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"io"
	"os"
)

var Path = "/home/code/LICENSE"

var sign = `304402200cf3fbba969cad28b904c33baa0c1dd973bd6d11b79b22f070a5602156405a0b0220371400e9446c3ddc13eae286ed36e793ba6505e375f63a0eb3d18342c52452bf`

var pubPEM = `-----BEGIN ECDSA PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMWv37ewL+vK1ynI2qQqX3QEtEcnG
5iKhrBxtkmYJgXhtaBDSNZikXu9045E5EJBPFfWolaWHFKIN1A0h/xY6Cg==
-----END ECDSA PUBLIC KEY-----`

func main() {
	pubPEMBlock, _ := pem.Decode([]byte(pubPEM))

	if pubPEMBlock == nil {
		println("invalid public key")
		return
	}

	der, _ := x509.ParsePKIXPublicKey(pubPEMBlock.Bytes)

	ecdsaDer := der.(*ecdsa.PublicKey)

	f, _ := os.Open(Path)

	hasher := sha256.New()

	io.Copy(hasher, f)

	fileHash := hasher.Sum(nil)

	sigDecoded := make([]byte, len(sign))
	n, _ := hex.Decode(sigDecoded, []byte(sign))

	if ok := ecdsa.VerifyASN1(ecdsaDer, fileHash, sigDecoded[:n]); ok {
		fmt.Printf("file: %s\nsign: %x\nis correct signature\n", Path, sigDecoded[:n])
	} else {
		fmt.Printf("file: %s\nsign: %x\nis invalid signature\n", Path, sigDecoded[:n])
	}
}
// 출력
file: /home/code/LICENSE
sign: 304402200cf3fbba969cad28b904c33baa0c1dd973bd6d11b79b22f070a5602156405a0b0220371400e9446c3ddc13eae286ed36e793ba6505e375f63a0eb3d18342c52452bf
is correct signature

ECDH

ECDH 는 타원 곡선 디피-헬만(Elliptic-curve Diffie–Hellman) 알고리즘입니다.

타원 곡선을 사용하여 기존 디피-헬만 방식보다 더 짧은키와 더 빠른 연산속도로 상대측과 공유키를 만들 수 있습니다.

ECDH 함수 예제

func GenerateSharedKey(localPriv *ecdsa.PrivateKey, remotePub *ecdsa.PublicKey) []byte {
	x, y := remotePub.Curve.ScalarMult(remotePub.X, remotePub.Y, localPriv.D.Bytes())
	// 상대측과 동일한 x, y 값을 가짐

	hasher := sha256.New()
	hasher.Write(x.Bytes())
	hasher.Write(y.Bytes())
	return hasher.Sum(nil)
}

각각의 호스트는 상대측 호스트의 공개키를 사전에 공유해야 합니다.