crypto.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package cert
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/rand"
  6. "fmt"
  7. "io"
  8. "golang.org/x/crypto/argon2"
  9. )
  10. // KDF factors
  11. type Argon2Parameters struct {
  12. version rune
  13. Memory uint32 // KiB
  14. Parallelism uint8
  15. Iterations uint32
  16. salt []byte
  17. }
  18. // Returns a new Argon2Parameters object with current version set
  19. func NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters {
  20. return &Argon2Parameters{
  21. version: argon2.Version,
  22. Memory: memory, // KiB
  23. Parallelism: parallelism,
  24. Iterations: iterations,
  25. }
  26. }
  27. // Encrypts data using AES-256-GCM and the Argon2id key derivation function
  28. func aes256Encrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
  29. key, err := aes256DeriveKey(passphrase, kdfParams)
  30. if err != nil {
  31. return nil, err
  32. }
  33. // this should never happen, but since this dictates how our calls into the
  34. // aes package behave and could be catastraphic, let's sanity check this
  35. if len(key) != 32 {
  36. return nil, fmt.Errorf("invalid AES-256 key length (%d) - cowardly refusing to encrypt", len(key))
  37. }
  38. block, err := aes.NewCipher(key)
  39. if err != nil {
  40. return nil, err
  41. }
  42. gcm, err := cipher.NewGCM(block)
  43. if err != nil {
  44. return nil, err
  45. }
  46. nonce := make([]byte, gcm.NonceSize())
  47. if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
  48. return nil, err
  49. }
  50. ciphertext := gcm.Seal(nil, nonce, data, nil)
  51. blob := joinNonceCiphertext(nonce, ciphertext)
  52. return blob, nil
  53. }
  54. // Decrypts data using AES-256-GCM and the Argon2id key derivation function
  55. // Expects the data to include an Argon2id parameter string before the encrypted data
  56. func aes256Decrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
  57. key, err := aes256DeriveKey(passphrase, kdfParams)
  58. if err != nil {
  59. return nil, err
  60. }
  61. block, err := aes.NewCipher(key)
  62. if err != nil {
  63. return nil, err
  64. }
  65. gcm, err := cipher.NewGCM(block)
  66. if err != nil {
  67. return nil, err
  68. }
  69. nonce, ciphertext, err := splitNonceCiphertext(data, gcm.NonceSize())
  70. if err != nil {
  71. return nil, err
  72. }
  73. plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
  74. if err != nil {
  75. return nil, fmt.Errorf("invalid passphrase or corrupt private key")
  76. }
  77. return plaintext, nil
  78. }
  79. func aes256DeriveKey(passphrase []byte, params *Argon2Parameters) ([]byte, error) {
  80. if params.salt == nil {
  81. params.salt = make([]byte, 32)
  82. if _, err := rand.Read(params.salt); err != nil {
  83. return nil, err
  84. }
  85. }
  86. // keySize of 32 bytes will result in AES-256 encryption
  87. key, err := deriveKey(passphrase, 32, params)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return key, nil
  92. }
  93. // Derives a key from a passphrase using Argon2id
  94. func deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {
  95. if params.version != argon2.Version {
  96. return nil, fmt.Errorf("incompatible Argon2 version: %d", params.version)
  97. }
  98. if params.salt == nil {
  99. return nil, fmt.Errorf("salt must be set in argon2Parameters")
  100. } else if len(params.salt) < 16 {
  101. return nil, fmt.Errorf("salt must be at least 128 bits")
  102. }
  103. key := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)
  104. return key, nil
  105. }
  106. // Prepends nonce to ciphertext
  107. func joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {
  108. return append(nonce, ciphertext...)
  109. }
  110. // Splits nonce from ciphertext
  111. func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
  112. if len(blob) <= nonceSize {
  113. return nil, nil, fmt.Errorf("invalid ciphertext blob - blob shorter than nonce length")
  114. }
  115. return blob[:nonceSize], blob[nonceSize:], nil
  116. }