crypto

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2026 License: Apache-2.0, MIT Imports: 5 Imported by: 9

README

Crypto package

This crypto package is a thin ergonomic layer on top of the normal golang crypto packages or x/crypto.

It aims to solve the following problems with the standard crypto packages:

  • different algorithms have different APIs and ergonomics, which makes it hard to use them interchangeably
  • occasionally, it's quite hard to figure out how to do simple tasks (like encoding/decoding keys)
  • it's still necessary to make some educated choices (e.g. which hash function to use for signatures)
  • sometimes features are left out (e.g. ed25519 to X25519 for key exchange, secp256k1...)
  • some hash functions are not available in the standard library with no easy way to extend it (e.g. KECCAK-256)

To do so, this package provides and implements a set of shared interfaces for all algorithms. As not all algorithms support all features (e.g. RSA keys don't support key exchange), some interfaces are optionally implemented.

An additional benefit of shared interfaces is that a shared test suite can be written to test all algorithms, which this package does.

Note: this is not a dig or a criticism of the golang crypto packages, just an attempt to make them easier to use.

Example

// This example demonstrates how to use the crypto package without going over all the features.
// We will use P-256 keys, but they all work the same way (although not all have all the features).

// 0: Generate a key pair
pubAlice, privAlice, err := p256.GenerateKeyPair()
handleErr(err)

// 1: Serialize a key, read it back, verify it's the same
privAliceBytes := privAlice.ToPKCS8DER()
privAlice2, err := p256.PrivateKeyFromPKCS8DER(privAliceBytes)
handleErr(err)
fmt.Println("Keys are equals:", privAlice.Equal(privAlice2))

// 2: Sign a message, verify the signature.
// Signatures can be made in raw bytes (SignToBytes) or ASN.1 DER format (SignToASN1).
msg := []byte("hello world")
sig, err := privAlice.SignToBytes(msg)
handleErr(err)
fmt.Println("Signature is valid:", pubAlice.VerifyBytes(msg, sig))

// 3: Signatures are done with an opinionated default configuration, but you can override it.
// For example, the default hash function for P-256 is SHA-256, but you can use SHA-384 instead.
opts := []crypto.SigningOption{crypto.WithSigningHash(crypto.SHA384)}
sig384, err := privAlice.SignToBytes(msg, opts...)
handleErr(err)
fmt.Println("Signature is valid (SHA-384):", pubAlice.VerifyBytes(msg, sig384, opts...))

// 4: Key exchange: generate a second key-pair and compute a shared secret.
// ⚠️ Security Warning: The shared secret returned by key agreement should NOT be used directly as an encryption key.
// It must be processed through a Key Derivation Function (KDF) such as HKDF before being used in cryptographic protocols.
// Using the raw shared secret directly can lead to security vulnerabilities.
pubBob, privBob, err := p256.GenerateKeyPair()
handleErr(err)
shared1, err := privAlice.KeyExchange(pubBob)
handleErr(err)
shared2, err := privBob.KeyExchange(pubAlice)
handleErr(err)
fmt.Println("Shared secrets are identical:", bytes.Equal(shared1, shared2))

// 5: Bonus: one very annoying thing in cryptographic protocols is that the other side needs to know the configuration
// you used for your signature. Having defaults or implied config only work sor far.
// To solve this problem, this package integrates varsig: a format to describe the signing configuration. This varsig
// can be attached to the signature, and the other side doesn't have to guess any more. Here is how it works:
varsigBytes := privAlice.Varsig(opts...).Encode()
fmt.Println("Varsig:", base64.StdEncoding.EncodeToString(varsigBytes))
sig, err = privAlice.SignToBytes(msg, opts...)
handleErr(err)
varsigDecoded, err := varsig.Decode(varsigBytes)
handleErr(err)
fmt.Println("Signature with varsig is valid:", pubAlice.VerifyBytes(msg, sig, crypto.WithVarsig(varsigDecoded)))

// Output:
// Keys are equals: true
// Signature is valid: true
// Signature is valid (SHA-384): true
// Shared secrets are identical: true
// Varsig: NAHsAYAkIF8=
// Signature with varsig is valid: true

Supported Cryptographic Algorithms

Algorithm Signature Format Public Key Formats Private Key Formats Key Agreement
Ed25519 Raw bytes, ASN.1 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM ✅ (via X25519)
ECDSA P-256 Raw bytes, ASN.1 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM
ECDSA P-384 Raw bytes, ASN.1 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM
ECDSA P-521 Raw bytes, ASN.1 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM
ECDSA secp256k1 Raw bytes, ASN.1 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM
RSA PKCS#1 v1.5 ASN.1 X.509 DER/PEM, Multibase PKCS#8 DER/PEM
X25519 Raw bytes, X.509 DER/PEM, Multibase Raw bytes, PKCS#8 DER/PEM

Documentation

Overview

Package crypto is a thin ergonomic layer on top of the normal golang crypto packages or `x/crypto`.

It aims to solve the following problems with the standard crypto packages:

  • different algorithms have different APIs and ergonomics, which makes it hard to use them interchangeably
  • occasionally, it's quite hard to figure out how to do simple tasks (like encoding/decoding keys)
  • it's still necessary to make some educated choices (e.g. which hash function to use for signatures)
  • sometimes features are left out (e.g. ed25519 to X25519 for key exchange, secp256k1...)
  • some hash functions are not available in the standard library with no easy way to extend it (e.g. KECCAK-256)

To do so, this package provides and implements a set of shared interfaces for all algorithms. As not all algorithms support all features (e.g. RSA keys don't support key exchange), some interfaces are optionally implemented.

An additional benefit of shared interfaces is that a shared test suite can be written to test all algorithms, which this package does.

Example
package main

import (
	"bytes"
	"encoding/base64"
	"fmt"

	"github.com/ucan-wg/go-varsig"

	"code.sonr.org/go/did-it/crypto"
	"code.sonr.org/go/did-it/crypto/p256"
)

func main() {
	// This example demonstrates how to use the crypto package without going over all the features.
	// We will use P-256 keys, but they all work the same way (although not all have all the features).

	// 0: Generate a key pair
	pubAlice, privAlice, err := p256.GenerateKeyPair()
	handleErr(err)

	// 1: Serialize a key, read it back, verify it's the same
	privAliceBytes := privAlice.ToPKCS8DER()
	privAlice2, err := p256.PrivateKeyFromPKCS8DER(privAliceBytes)
	handleErr(err)
	fmt.Println("Keys are equals:", privAlice.Equal(privAlice2))

	// 2: Sign a message, verify the signature.
	// Signatures can be made in raw bytes (SignToBytes) or ASN.1 DER format (SignToASN1).
	msg := []byte("hello world")
	sig, err := privAlice.SignToBytes(msg)
	handleErr(err)
	fmt.Println("Signature is valid:", pubAlice.VerifyBytes(msg, sig))

	// 3: Signatures are done with an opinionated default configuration, but you can override it.
	// For example, the default hash function for P-256 is SHA-256, but you can use SHA-384 instead.
	opts := []crypto.SigningOption{crypto.WithSigningHash(crypto.SHA384)}
	sig384, err := privAlice.SignToBytes(msg, opts...)
	handleErr(err)
	fmt.Println("Signature is valid (SHA-384):", pubAlice.VerifyBytes(msg, sig384, opts...))

	// 4: Key exchange: generate a second key-pair and compute a shared secret.
	// ⚠️ Security Warning: The shared secret returned by key agreement should NOT be used directly as an encryption key.
	// It must be processed through a Key Derivation Function (KDF) such as HKDF before being used in cryptographic protocols.
	// Using the raw shared secret directly can lead to security vulnerabilities.
	pubBob, privBob, err := p256.GenerateKeyPair()
	handleErr(err)
	shared1, err := privAlice.KeyExchange(pubBob)
	handleErr(err)
	shared2, err := privBob.KeyExchange(pubAlice)
	handleErr(err)
	fmt.Println("Shared secrets are identical:", bytes.Equal(shared1, shared2))

	// 5: Bonus: one very annoying thing in cryptographic protocols is that the other side needs to know the configuration
	// you used for your signature. Having defaults or implied config only work sor far.
	// To solve this problem, this package integrates varsig: a format to describe the signing configuration. This varsig
	// can be attached to the signature, and the other side doesn't have to guess any more. Here is how it works:
	varsigBytes := privAlice.Varsig(opts...).Encode()
	fmt.Println("Varsig:", base64.StdEncoding.EncodeToString(varsigBytes))
	sig, err = privAlice.SignToBytes(msg, opts...)
	handleErr(err)
	varsigDecoded, err := varsig.Decode(varsigBytes)
	handleErr(err)
	fmt.Println("Signature with varsig is valid:", pubAlice.VerifyBytes(msg, sig, crypto.WithVarsig(varsigDecoded)))

}

func handleErr(err error) {
	if err != nil {
		panic(err)
	}
}
Output:

Keys are equals: true
Signature is valid: true
Signature is valid (SHA-384): true
Shared secrets are identical: true
Varsig: NAHsAYAkIF8=
Signature with varsig is valid: true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Hash

type Hash uint

Hash is similar to crypto.Hash but can be extended with more values.

const (
	// From "crypto"
	MD4         Hash = 1 + iota // import golang.org/x/crypto/md4
	MD5                         // import crypto/md5
	SHA1                        // import crypto/sha1
	SHA224                      // import crypto/sha256
	SHA256                      // import crypto/sha256
	SHA384                      // import crypto/sha512
	SHA512                      // import crypto/sha512
	MD5SHA1                     // no implementation; MD5+SHA1 used for TLS RSA
	RIPEMD160                   // import golang.org/x/crypto/ripemd160
	SHA3_224                    // import golang.org/x/crypto/sha3
	SHA3_256                    // import golang.org/x/crypto/sha3
	SHA3_384                    // import golang.org/x/crypto/sha3
	SHA3_512                    // import golang.org/x/crypto/sha3
	SHA512_224                  // import crypto/sha512
	SHA512_256                  // import crypto/sha512
	BLAKE2s_256                 // import golang.org/x/crypto/blake2s
	BLAKE2b_256                 // import golang.org/x/crypto/blake2b
	BLAKE2b_384                 // import golang.org/x/crypto/blake2b
	BLAKE2b_512                 // import golang.org/x/crypto/blake2b

	// Extensions
	KECCAK_256
	KECCAK_512
)

func FromVarsigHash

func FromVarsigHash(h varsig.Hash) Hash

FromVarsigHash converts a varsig.Hash value to the corresponding Hash value.

func (Hash) HashFunc

func (h Hash) HashFunc() Hash

HashFunc simply returns the value of h so that Hash implements [SignerOpts].

func (Hash) New

func (h Hash) New() hash.Hash

New returns a new hash.Hash calculating the given hash function. New panics if the hash function is not linked into the binary.

func (Hash) String

func (h Hash) String() string

String returns the name of the hash function.

func (Hash) ToVarsigHash

func (h Hash) ToVarsigHash() varsig.Hash

ToVarsigHash returns the corresponding varsig.Hash value.

type PrivateKey

type PrivateKey interface {
	// Equal returns true if other is the same PrivateKey
	Equal(other PrivateKey) bool

	// Public returns the matching PublicKey.
	Public() PublicKey

	// ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format.
	ToPKCS8DER() []byte

	// ToPKCS8PEM serializes the PrivateKey into the PKCS#8 PEM (string) format.
	ToPKCS8PEM() string
}

type PrivateKeyKeyExchange

type PrivateKeyKeyExchange interface {
	PrivateKey

	// PublicKeyIsCompatible checks that the given PublicKey is compatible to perform key exchange.
	PublicKeyIsCompatible(remote PublicKey) bool

	// KeyExchange computes the shared key using the given PublicKey.
	KeyExchange(remote PublicKey) ([]byte, error)
}

type PrivateKeySigningASN1

type PrivateKeySigningASN1 interface {
	PrivateKey

	// Varsig returns the varsig.Varsig corresponding to the given parameters and private key.
	Varsig(opts ...SigningOption) varsig.Varsig

	// SignToASN1 creates a signature in the ASN.1 format.
	SignToASN1(message []byte, opts ...SigningOption) ([]byte, error)
}

type PrivateKeySigningBytes

type PrivateKeySigningBytes interface {
	PrivateKey

	// Varsig returns the varsig.Varsig corresponding to the given parameters and private key.
	Varsig(opts ...SigningOption) varsig.Varsig

	// SignToBytes creates a signature in the "raw bytes" format.
	// This format can make some assumptions and may not be what you expect.
	// Ideally, this format is defined by the same specification as the underlying crypto scheme.
	SignToBytes(message []byte, opts ...SigningOption) ([]byte, error)
}

type PrivateKeyToBytes

type PrivateKeyToBytes interface {
	PrivateKey

	// ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure.
	// This format can make some assumptions and may not be what you expect.
	// Ideally, this format is defined by the same specification as the underlying crypto scheme.
	ToBytes() []byte
}

type PublicKey

type PublicKey interface {
	// Equal returns true if other is the same PublicKey
	Equal(other PublicKey) bool

	// ToPublicKeyMultibase format the PublicKey into a string compatible with a PublicKeyMultibase field
	// in a DID Document.
	ToPublicKeyMultibase() string

	// ToX509DER serializes the PublicKey into the X.509 DER (binary) format.
	ToX509DER() []byte

	// ToX509PEM serializes the PublicKey into the X.509 PEM (string) format.
	ToX509PEM() string
}

type PublicKeySigningASN1

type PublicKeySigningASN1 interface {
	PublicKey

	// VerifyASN1 checks a signature in the ASN.1 format.
	VerifyASN1(message, signature []byte, opts ...SigningOption) bool
}

type PublicKeySigningBytes

type PublicKeySigningBytes interface {
	PublicKey

	// VerifyBytes checks a signature in the "raw bytes" format.
	// This format can make some assumptions and may not be what you expect.
	// Ideally, this format is defined by the same specification as the underlying crypto scheme.
	VerifyBytes(message, signature []byte, opts ...SigningOption) bool
}

type PublicKeyToBytes

type PublicKeyToBytes interface {
	PublicKey

	// ToBytes serializes the PublicKey into "raw bytes", without metadata or structure.
	// This format can make some assumptions and may not be what you expect.
	// Ideally, this format is defined by the same specification as the underlying crypto scheme.
	ToBytes() []byte
}

type SigningOption

type SigningOption func(opts *SigningOpts)

func WithPayloadEncoding

func WithPayloadEncoding(encoding varsig.PayloadEncoding) SigningOption

WithPayloadEncoding specify the encoding that was used on the message before signing it. This will be included in the resulting varsig.

func WithSigningHash

func WithSigningHash(hash Hash) SigningOption

WithSigningHash specify the hash algorithm to be used for signatures

func WithVarsig

func WithVarsig(vsig varsig.Varsig) SigningOption

WithVarsig configure the signing or verification parameters from a varsig. If you use WithVarsig, you should NOT use other options.

type SigningOpts

type SigningOpts struct {
	// contains filtered or unexported fields
}

SigningOpts contains the resulting signature configuration.

func CollectSigningOptions

func CollectSigningOptions(opts []SigningOption) SigningOpts

CollectSigningOptions collects the signing options into a SigningOpts.

func (SigningOpts) HashOrDefault

func (opts SigningOpts) HashOrDefault(_default Hash) Hash

HashOrDefault returns the hash algorithm to be used for signatures, or the default if not specified.

func (SigningOpts) PayloadEncoding

func (opts SigningOpts) PayloadEncoding() varsig.PayloadEncoding

PayloadEncoding returns the encoding used on the message before signing it.

func (SigningOpts) VarsigMatch

func (opts SigningOpts) VarsigMatch(algo varsig.Algorithm, curve uint64, keyLength uint64) bool

VarsigMatch returns true if the given varsig parameters match the signing options.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL