rsa 키를 네트워크, 파일 등으로 작성하는 경우도 종종 있습니다.

이러한 경우, rsa 키를 인/디코딩 하는 표준이 존재합니다.

x509 패키지에서 PKCS#1 PKCS#8 PKIX 구현체를 사용하여 rsa 키를 바이트 슬라이스로 파싱해 보겠습니다.

그리고 이 데이터를 PEM 으로 인/디코딩 해보겠습니다.

비밀키

마샬

OpenSSL 등에서 rsa 키는 PKCS#1 으로 비밀키로 출력합니다.

공개키 인코딩은 다음 함수를 사용합니다.

privData1 := x509.MarshalPKCS1PrivateKey(priv)
privData8, err := x509.MarshalPKCS8PrivateKey(priv)

PKCS#1rsa 비밀키만 지원하고, PKCS#8rsa, ecdsa, ed25519 를 지원합니다.

// rsa 비밀키를 바이트 슬라이스로 저장, 출력 예시
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"fmt"
)

func main() {
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)

	privData := x509.MarshalPKCS1PrivateKey(priv)

	fmt.Printf("%x\n", privData)
}
// 출력
3082025c02010002818100e872fa8b69550f70e00dae7e48a9eaa922f0b77b1ae137ad0693ac1ca6d4b9412b5f6b213aaa9959c5772c0dcb3682e0952d26b43ee74443a13fdc186757f1b1f6650ab3f30d1d6c9bd8b5b422bedaa4b67439199eb7b3f857019000ac83416793b35d7e840dfed47b892ed5efcf1d211b57d2da0cf918cf8122ca458c7f2cd702030100010281803570b1af397127d2a08024bcbdc1eac425d747a792bf728861f9b35906f5e2fd5b965d4fcf6807477f416dac622acf1e08cdcf9722db1273a5efc38f0cbb48425c9fe4991bbbe4cb2b23f54fc54b8181270e6ebec5329525fc41e2b8ec8dcf124862c93e365b2087a1fece6d7abbd8363e4608994c068e49ff3ae0a557411069024100efed231bc9d4865bd248e3b301c888eba2eac6b9bf0145403443cb99b76ef3cfd1c418060c3fd308fa9dbf15e800edb1d3a9fe6ab2dc23fae171d3ff20c80a5d024100f8059a9bf1bf031bddd009abb90aba82c800c265c9a2b7a2500a76c1f030c4ddd1da9539ad495b2b56f1eb78da77f0e83aa473fd5e06037342019a32c7f7e8c302403972825189060615be480d9d32f41d34ae91a07ba12c60b47acf30d4e1830385e3281cc875ee624d681495485d80bb6934d12345d105bce7b94b54036689ff51024100dd5292df29e09bf39802773ec8072cdc5b8161f7d6f0df11767f7fee1c5a48b2f0fb2f42a2c1dd1b55a0d7d1a6927565a63eee4a6046d97956a37b68694d6e650240379863e269c5817e0449ac6839dd7bb0807269cbf90423e5a79ee3f5e27c3f0287a8221df334d7720777ffb262b93330b225d097fc4bfcab1a13e3aaa30b2ddc

PKCS#8 로 비밀키를 출력할 수 있습니다.

// PKCS8 예시
func main() {
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)

	privData1 := x509.MarshalPKCS1PrivateKey(priv)
	privData8, _ := x509.MarshalPKCS8PrivateKey(priv)

	fmt.Printf("PKCS1:\n%x\n\n", privData1)
	fmt.Printf("PKCS8:\n%x\n\n", privData8)
}
PKCS1:
3082025c02010002818100d747d9cd6c76b17b3e009560a31d79ea83bc53153cb9e5206a4f74df447a0fa2ab837a493be122ab3a749130caeb927b44b032260fe9c0435adb06e01822967afb8b707c92d47fb702f8c0b5be54744c236cd5978bae0bd826af997d6eae52b2dc8f37d4690a15b11aa02c66f4e3eaf8cabe2d68c64248bfe1aa565091e1ff9502030100010281803c270f6c9ec9eb7d8c2d8d8f9b04397495aaa16c65b36b9a4b1a3885ca147119fae299e0aca2939554980d999c99862c7b5ce026527bf604dbf6274da670c3aac628441e1301c208f7d83c681f16b46e1be36dda66a86abf120438fbe116a4d64edc56409193c513173a0b692f2661799025ebda7a1864df288bc80342466501024100e2988279ef851e5b7aa96306fb8ffd558bfb8e5f2c9735961fc9ccc887154987dc1bf312c9bb6145b4f32329d358e4e2ab2aa2e9eed6075d940be7c8e270d85d024100f33774937b62c5147cff576a278d0e1a438aa4305e27b47d58d46c301f5ecbec35503101099b1c1c4446aa93e1e9a18b3242bcb4aa331eb857fa94585868709902410099682905866c383634a012e3fa51ec700b3a3e941eab2c633b9832a51c1704560f6578b6228f52768fdfffd1643d8d900bd3ecc1b5a97f09129ed0c846cda11d024001eef9ed4638d5ca40e822c0c449b9c7f9ab7b7caaf2f3db5f3e6911c4de130ef0a98f8a2b926119d46d1f640792bce70762426fc93e98caec602097c734255902407409270f29f9e5583f83bb38f26ccdf9ae3cd54a325908cd07e447573e7443e5e0fb485b7839812b1147cf5114d62ec4308788c11f3333aa827a9f729f1446b0

PKCS8:
30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100d747d9cd6c76b17b3e009560a31d79ea83bc53153cb9e5206a4f74df447a0fa2ab837a493be122ab3a749130caeb927b44b032260fe9c0435adb06e01822967afb8b707c92d47fb702f8c0b5be54744c236cd5978bae0bd826af997d6eae52b2dc8f37d4690a15b11aa02c66f4e3eaf8cabe2d68c64248bfe1aa565091e1ff9502030100010281803c270f6c9ec9eb7d8c2d8d8f9b04397495aaa16c65b36b9a4b1a3885ca147119fae299e0aca2939554980d999c99862c7b5ce026527bf604dbf6274da670c3aac628441e1301c208f7d83c681f16b46e1be36dda66a86abf120438fbe116a4d64edc56409193c513173a0b692f2661799025ebda7a1864df288bc80342466501024100e2988279ef851e5b7aa96306fb8ffd558bfb8e5f2c9735961fc9ccc887154987dc1bf312c9bb6145b4f32329d358e4e2ab2aa2e9eed6075d940be7c8e270d85d024100f33774937b62c5147cff576a278d0e1a438aa4305e27b47d58d46c301f5ecbec35503101099b1c1c4446aa93e1e9a18b3242bcb4aa331eb857fa94585868709902410099682905866c383634a012e3fa51ec700b3a3e941eab2c633b9832a51c1704560f6578b6228f52768fdfffd1643d8d900bd3ecc1b5a97f09129ed0c846cda11d024001eef9ed4638d5ca40e822c0c449b9c7f9ab7b7caaf2f3db5f3e6911c4de130ef0a98f8a2b926119d46d1f640792bce70762426fc93e98caec602097c734255902407409270f29f9e5583f83bb38f26ccdf9ae3cd54a325908cd07e447573e7443e5e0fb485b7839812b1147cf5114d62ec4308788c11f3333aa827a9f729f1446b0

두 함수는 엄연히 출력 결과가 다르므로, 상황에 따라 알맞는 함수를 사용하면 됩니다.

파싱

다음 함수로 공개키를 파싱합니다.

priv1, err := x509.ParsePKCS1PrivateKey(der)
priv8, err := x509.ParsePKCS8PrivateKey(der)

x509.ParsePKCS1PrivateKey() 함수는 rsa.PrivateKey 를 리턴하지만,
x509.ParsePKCS8PrivateKey() 함수는 interface{} 를 리턴합니다.

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"fmt"
)

func main() {
	// 비밀키 생성
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)

	// 동일한 비밀키를 PKCS#1, PKCS#8 로 각각 마샬
	privData1 := x509.MarshalPKCS1PrivateKey(priv)
	privData8, _ := x509.MarshalPKCS8PrivateKey(priv)

	// 각각 파싱
	priv1, _ := x509.ParsePKCS1PrivateKey(privData1)
	parsedData, _ := x509.ParsePKCS8PrivateKey(privData8)

	// x509.ParsePKCS8PrivateKey() 는 interface{} 로 리턴됨
	// 여러 종류의 비밀키를 지원하기 때문
	// 이를 사용할 수 있게 *rsa.PrivateKey 로 캐스팅
	priv8 := parsedData.(*rsa.PrivateKey)

	// priv1의 공개키로 암호화
	cip, _ := rsa.EncryptPKCS1v15(rand.Reader, &priv1.PublicKey, []byte("hi"))

	// priv8의 비밀키로 복호화
	plain, _ := rsa.DecryptPKCS1v15(rand.Reader, priv8, cip)

	fmt.Printf("%s\n", plain)
}
// 출력
hi

공개키

마샬

공개키는 다음 함수를 사용합니다.

pubData, err := x509.MarshalPKIXPublicKey(&pub)

rsa, ecdsa, ed25519 공개키를 지원합니다.

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"fmt"
)

func main() {
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)
	pub := priv.PublicKey
	pubData, _ := x509.MarshalPKIXPublicKey(&pub)

	fmt.Printf("%x\n", pubData)
}
// 출력
30819f300d06092a864886f70d010101050003818d0030818902818100b3c5c7b1b67cf16d7eb61a2de12d15abb70f778725a36b6a699eeddba9e2527d1a56edeeae7ce2ba40215c4a56bed84a5fc3bd5d64bf5854e7114568cf30c244b92ab745a95298baf0bcca5a7d3863938c822421aefeb5590b89815229f0649871386ce3ad2829000686d4e3ca4fde6c4994bec7fb68e76ebb6b7e2302ea95c90203010001

파싱

다음 함수로 데이터를 파싱합니다.

parsed, err := x509.ParsePKIXPublicKey(der)

interface{} 를 리턴하기 때문에, 캐스팅이 필요합니다.

// 예시
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
)

func main() {
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)
	pub := priv.PublicKey
	der, _ := x509.MarshalPKIXPublicKey(pub)

	parsedPub, _ := x509.ParsePKIXPublicKey(der)
    
    rsaPub := parsedPub(*rsa.PublicKey)
}

PEM 사용하기

OpenSSL 등 인증서를 활용하는 여러 프로그램은 PEM 으로 인코딩된 인증서를 사용합니다.

go 에서도 PEM 인코딩, 디코딩을 지원합니다.

인코딩

block := pem.Block{
	Type:  "RSA PUBLIC KEY",	// PEM 제목
	Bytes: pubBytes,			// 데이터, 공개/비밀키 바이트 슬라이스 입력
}
// pem 블록 정의

pemEncoded := pem.EncodeToMemory(&block)
// 바이트 리턴, 잘못된 값을 입력하면 nil 리턴

f, _ := os.Create("pub.pem")
err := pem.Encode(f, &block)
// 스트림에 인코딩 데이터 작성
// pem 인코딩 예시
package main

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

func main() {
	priv, _ := rsa.GenerateKey(rand.Reader, 1024)

	privData1 := x509.MarshalPKCS1PrivateKey(priv)

	block := pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: privData1,
	}

	pemEncoded := pem.EncodeToMemory(&block)

	fmt.Printf("%s\n", pemEncoded)
}
// 출력
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCqmc6dxR+tsJsGPF00Wr3qmV/3ApwE1DEarZ9BSiTYTQVUDy+W
uZ+niQFi6C6V5RceEKAP1h7/Z1LTWPkaJ8wyJzFip0hD9iEei4SI6NPQZLDPmPYY
sXKBVUFpnfqQ8rIH+kv2d2OG2BXDp16LGYjHTdr8JJEm1aQ+AjWZh9pzbQIDAQAB
AoGANS/eirF6PtxgeIE5Tak8rHdEw+28VoURChA3JlPHSOg6UQqq+4LDk6fTFtLs
My9JFcZ5IHbREy9TUzDZ+J2Pu1BnjUi+G2m54sdJLglc5sD2zqpJmfxGSFGEBKqt
If2G22Fizo2UD8pfmK7tPvy1pYpHt2J7fmeR1uQzYq4zXMECQQDiAZnrAjJITLmu
kq9IWmwfRfiiPYwGYaidcrHzrfCwYTItFX9UMhyLRZm7zWy1Kpos6KZnQUUOYP7D
enaQSBP3AkEAwT3YkaGht/y7c4xYfg6KL8wSfVVDbFC9J+qBWrCzxYM98VHuNjR0
iyq84hIMBkoZ4m8072S4PiNkQZv6QHeSuwJBAKG2PZjPSIU9CPtlj6/4qzaxTVdh
LIkAZbLK95OBmR/LXCiwIhxvgscQdRDQywDSS+DoUvC83hmMw53BSYaxXD8CQAl/
VaaKsB0P0dKzAiJn6ojA2ePJDgBD05gjoWnop108vw2ePjvxxgyU9CWUR30DpVQI
rSxa4edD7AiBdwI2HkMCQQDKb+XNhoLhK8/RMylbDIs6V9Ushmic1OYBV2uNqYJF
yJoQR1+12jndEMAa8y0uVFxxCxBlw1a/CYVsBIwYpuhu
-----END RSA PRIVATE KEY-----

디코딩

block, rest := pem.Decode(privPEM)

PEM 슬라이스를 입력하여, PEM 블록을 얻습니다.

잘못된 데이터를 입력하면, blocknil 을, rest 에 입력한 데이터를 리턴합니다.

// 예시
package main

import (
	"encoding/pem"
	"fmt"
)

var privPEM = `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCqmc6dxR+tsJsGPF00Wr3qmV/3ApwE1DEarZ9BSiTYTQVUDy+W
uZ+niQFi6C6V5RceEKAP1h7/Z1LTWPkaJ8wyJzFip0hD9iEei4SI6NPQZLDPmPYY
sXKBVUFpnfqQ8rIH+kv2d2OG2BXDp16LGYjHTdr8JJEm1aQ+AjWZh9pzbQIDAQAB
AoGANS/eirF6PtxgeIE5Tak8rHdEw+28VoURChA3JlPHSOg6UQqq+4LDk6fTFtLs
My9JFcZ5IHbREy9TUzDZ+J2Pu1BnjUi+G2m54sdJLglc5sD2zqpJmfxGSFGEBKqt
If2G22Fizo2UD8pfmK7tPvy1pYpHt2J7fmeR1uQzYq4zXMECQQDiAZnrAjJITLmu
kq9IWmwfRfiiPYwGYaidcrHzrfCwYTItFX9UMhyLRZm7zWy1Kpos6KZnQUUOYP7D
enaQSBP3AkEAwT3YkaGht/y7c4xYfg6KL8wSfVVDbFC9J+qBWrCzxYM98VHuNjR0
iyq84hIMBkoZ4m8072S4PiNkQZv6QHeSuwJBAKG2PZjPSIU9CPtlj6/4qzaxTVdh
LIkAZbLK95OBmR/LXCiwIhxvgscQdRDQywDSS+DoUvC83hmMw53BSYaxXD8CQAl/
VaaKsB0P0dKzAiJn6ojA2ePJDgBD05gjoWnop108vw2ePjvxxgyU9CWUR30DpVQI
rSxa4edD7AiBdwI2HkMCQQDKb+XNhoLhK8/RMylbDIs6V9Ushmic1OYBV2uNqYJF
yJoQR1+12jndEMAa8y0uVFxxCxBlw1a/CYVsBIwYpuhu
-----END RSA PRIVATE KEY-----`

func main() {
	block, _ := pem.Decode([]byte(privPEM))

	fmt.Printf("type: %s\n", block.Type)
	fmt.Printf("key: \n%x\n", block.Bytes)
}
// 출력
type: RSA PRIVATE KEY
key: 
3082025d02010002818100aa99ce9dc51fadb09b063c5d345abdea995ff7029c04d4311aad9f414a24d84d05540f2f96b99fa7890162e82e95e5171e10a00fd61eff6752d358f91a27cc32273162a74843f6211e8b8488e8d3d064b0cf98f618b172815541699dfa90f2b207fa4bf6776386d815c3a75e8b1988c74ddafc249126d5a43e02359987da736d0203010001028180352fde8ab17a3edc607881394da93cac7744c3edbc5685110a10372653c748e83a510aaafb82c393a7d316d2ec332f4915c6792076d1132f535330d9f89d8fbb50678d48be1b69b9e2c7492e095ce6c0f6ceaa4999fc4648518404aaad21fd86db6162ce8d940fca5f98aeed3efcb5a58a47b7627b7e6791d6e43362ae335cc1024100e20199eb0232484cb9ae92af485a6c1f45f8a23d8c0661a89d72b1f3adf0b061322d157f54321c8b4599bbcd6cb52a9a2ce8a66741450e60fec37a76904813f7024100c13dd891a1a1b7fcbb738c587e0e8a2fcc127d55436c50bd27ea815ab0b3c5833df151ee3634748b2abce2120c064a19e26f34ef64b83e2364419bfa407792bb024100a1b63d98cf48853d08fb658faff8ab36b14d57612c890065b2caf79381991fcb5c28b0221c6f82c7107510d0cb00d24be0e852f0bcde198cc39dc14986b15c3f0240097f55a68ab01d0fd1d2b3022267ea88c0d9e3c90e0043d39823a169e8a75d3cbf0d9e3e3bf1c60c94f42594477d03a55408ad2c5ae1e743ec08817702361e43024100ca6fe5cd8682e12bcfd133295b0c8b3a57d52c86689cd4e601576b8da98245c89a10475fb5da39dd10c01af32d2e545c710b1065c356bf09856c048c18a6e86e