Skip to content

Commit 750f2d4

Browse files
committed
document the crypto package
1 parent 98bf1e4 commit 750f2d4

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

crypto/Readme.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Crypto package
2+
3+
This crypto package is a thin ergonomic layer on top of the normal golang crypto packages or `x/crypto`.
4+
5+
It aims to solve the following problems with the standard crypto packages:
6+
- different algorithms have different APIs and ergonomics, which makes it hard to use them interchangeably
7+
- occasionally, it's quite hard to figure out how to do simple tasks (like encoding/decoding keys)
8+
- it's still necessary to make some educated choices (e.g. which hash function to use for signatures)
9+
- sometimes features are left out (e.g. ed25519 to X25519 for key exchange, secp256k1...)
10+
- some hash functions are not available in the standard library with no easy way to extend it (e.g. KECCAK-256)
11+
12+
To do so, this package provides and implements a set of shared interfaces for all algorithms. As not all algorithms
13+
support all features (e.g. RSA keys don't support key exchange), some interfaces are optionally implemented.
14+
15+
An additional benefit of shared interfaces is that a shared test suite can be written to test all algorithms, which this
16+
package does.
17+
18+
## Example
19+
20+
```go
21+
// This example demonstrates how to use the crypto package without going over all the features.
22+
// We will use P-256 keys, but they all work the same way (although not all have all the features).
23+
24+
// 0: Generate a key pair
25+
pubAlice, privAlice, err := p256.GenerateKeyPair()
26+
handleErr(err)
27+
28+
// 1: Serialize a key, read it back, verify it's the same
29+
privAliceBytes := privAlice.ToPKCS8DER()
30+
privAlice2, err := p256.PrivateKeyFromPKCS8DER(privAliceBytes)
31+
handleErr(err)
32+
fmt.Println("Keys are equals:", privAlice.Equal(privAlice2))
33+
34+
// 2: Sign a message, verify the signature.
35+
// Signatures can be made in raw bytes (SignToBytes) or ASN.1 DER format (SignToASN1).
36+
msg := []byte("hello world")
37+
sig, err := privAlice.SignToBytes(msg)
38+
handleErr(err)
39+
fmt.Println("Signature is valid:", pubAlice.VerifyBytes(msg, sig))
40+
41+
// 3: Signatures are done with an opinionated default configuration, but you can override it.
42+
// For example, the default hash function for P-256 is SHA-256, but you can use SHA-384 instead.
43+
opts := []crypto.SigningOption{crypto.WithSigningHash(crypto.SHA384)}
44+
sig384, err := privAlice.SignToBytes(msg, opts...)
45+
handleErr(err)
46+
fmt.Println("Signature is valid (SHA-384):", pubAlice.VerifyBytes(msg, sig384, opts...))
47+
48+
// 4: Key exchange: generate a second key-pair and compute a shared secret.
49+
// ⚠️ Security Warning: The shared secret returned by key agreement should NOT be used directly as an encryption key.
50+
// It must be processed through a Key Derivation Function (KDF) such as HKDF before being used in cryptographic protocols.
51+
// Using the raw shared secret directly can lead to security vulnerabilities.
52+
pubBob, privBob, err := p256.GenerateKeyPair()
53+
handleErr(err)
54+
shared1, err := privAlice.KeyExchange(pubBob)
55+
handleErr(err)
56+
shared2, err := privBob.KeyExchange(pubAlice)
57+
handleErr(err)
58+
fmt.Println("Shared secrets are identical:", bytes.Equal(shared1, shared2))
59+
60+
// 5: Bonus: one very annoying thing in cryptographic protocols is that the other side needs to know the configuration
61+
// you used for your signature. Having defaults or implied config only work sor far.
62+
// To solve this problem, this package integrates varsig: a format to describe the signing configuration. This varsig
63+
// can be attached to the signature, and the other side doesn't have to guess any more. Here is how it works:
64+
varsigBytes := privAlice.Varsig(opts...).Encode()
65+
fmt.Println("Varsig:", base64.StdEncoding.EncodeToString(varsigBytes))
66+
sig, err = privAlice.SignToBytes(msg, opts...)
67+
handleErr(err)
68+
varsigDecoded, err := varsig.Decode(varsigBytes)
69+
handleErr(err)
70+
fmt.Println("Signature with varsig is valid:", pubAlice.VerifyBytes(msg, sig, crypto.WithVarsig(varsigDecoded)))
71+
72+
// Output:
73+
// Keys are equals: true
74+
// Signature is valid: true
75+
// Signature is valid (SHA-384): true
76+
// Shared secrets are identical: true
77+
// Varsig: NAHsAYAkIF8=
78+
// Signature with varsig is valid: true
79+
```
80+
81+
## Supported Cryptographic Algorithms
82+
83+
| Algorithm | Signature Format | Public Key Formats | Private Key Formats | Key Agreement |
84+
|-----------------|-------------------|-------------------------------------|---------------------------|----------------|
85+
| Ed25519 | Raw bytes, ASN.1 | Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM | ✅ (via X25519) |
86+
| ECDSA P-256 | Raw bytes, ASN.1 | Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM ||
87+
| ECDSA P-384 | Raw bytes, ASN.1 | Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM ||
88+
| ECDSA P-521 | Raw bytes, ASN.1 | Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM ||
89+
| ECDSA secp256k1 | Raw bytes, ASN.1 | Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM ||
90+
| RSA | PKCS#1 v1.5 ASN.1 | X.509 DER/PEM, Multibase | PKCS#8 DER/PEM ||
91+
| X25519 || Raw bytes, X.509 DER/PEM, Multibase | Raw bytes, PKCS#8 DER/PEM ||

crypto/_example/example_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package _example
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"fmt"
7+
8+
"github.com/ucan-wg/go-varsig"
9+
10+
"github.com/MetaMask/go-did-it/crypto"
11+
"github.com/MetaMask/go-did-it/crypto/p256"
12+
)
13+
14+
func Example() {
15+
// This example demonstrates how to use the crypto package without going over all the features.
16+
// We will use P-256 keys, but they all work the same way (although not all have all the features).
17+
18+
// 0: Generate a key pair
19+
pubAlice, privAlice, err := p256.GenerateKeyPair()
20+
handleErr(err)
21+
22+
// 1: Serialize a key, read it back, verify it's the same
23+
privAliceBytes := privAlice.ToPKCS8DER()
24+
privAlice2, err := p256.PrivateKeyFromPKCS8DER(privAliceBytes)
25+
handleErr(err)
26+
fmt.Println("Keys are equals:", privAlice.Equal(privAlice2))
27+
28+
// 2: Sign a message, verify the signature.
29+
// Signatures can be made in raw bytes (SignToBytes) or ASN.1 DER format (SignToASN1).
30+
msg := []byte("hello world")
31+
sig, err := privAlice.SignToBytes(msg)
32+
handleErr(err)
33+
fmt.Println("Signature is valid:", pubAlice.VerifyBytes(msg, sig))
34+
35+
// 3: Signatures are done with an opinionated default configuration, but you can override it.
36+
// For example, the default hash function for P-256 is SHA-256, but you can use SHA-384 instead.
37+
opts := []crypto.SigningOption{crypto.WithSigningHash(crypto.SHA384)}
38+
sig384, err := privAlice.SignToBytes(msg, opts...)
39+
handleErr(err)
40+
fmt.Println("Signature is valid (SHA-384):", pubAlice.VerifyBytes(msg, sig384, opts...))
41+
42+
// 4: Key exchange: generate a second key-pair and compute a shared secret.
43+
// ⚠️ Security Warning: The shared secret returned by key agreement should NOT be used directly as an encryption key.
44+
// It must be processed through a Key Derivation Function (KDF) such as HKDF before being used in cryptographic protocols.
45+
// Using the raw shared secret directly can lead to security vulnerabilities.
46+
pubBob, privBob, err := p256.GenerateKeyPair()
47+
handleErr(err)
48+
shared1, err := privAlice.KeyExchange(pubBob)
49+
handleErr(err)
50+
shared2, err := privBob.KeyExchange(pubAlice)
51+
handleErr(err)
52+
fmt.Println("Shared secrets are identical:", bytes.Equal(shared1, shared2))
53+
54+
// 5: Bonus: one very annoying thing in cryptographic protocols is that the other side needs to know the configuration
55+
// you used for your signature. Having defaults or implied config only work sor far.
56+
// To solve this problem, this package integrates varsig: a format to describe the signing configuration. This varsig
57+
// can be attached to the signature, and the other side doesn't have to guess any more. Here is how it works:
58+
varsigBytes := privAlice.Varsig(opts...).Encode()
59+
fmt.Println("Varsig:", base64.StdEncoding.EncodeToString(varsigBytes))
60+
sig, err = privAlice.SignToBytes(msg, opts...)
61+
handleErr(err)
62+
varsigDecoded, err := varsig.Decode(varsigBytes)
63+
handleErr(err)
64+
fmt.Println("Signature with varsig is valid:", pubAlice.VerifyBytes(msg, sig, crypto.WithVarsig(varsigDecoded)))
65+
66+
// Output:
67+
// Keys are equals: true
68+
// Signature is valid: true
69+
// Signature is valid (SHA-384): true
70+
// Shared secrets are identical: true
71+
// Varsig: NAHsAYAkIF8=
72+
// Signature with varsig is valid: true
73+
}
74+
75+
func handleErr(err error) {
76+
if err != nil {
77+
panic(err)
78+
}
79+
}

0 commit comments

Comments
 (0)