github.com/trustbloc/kms-go@v1.1.2/secretlock/local/masterlock/hkdf/master_secret_lock.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 SPDX-License-Identifier: Apache-2.0 4 */ 5 6 package hkdf 7 8 import ( 9 "crypto/cipher" 10 "crypto/sha256" 11 "encoding/base64" 12 "fmt" 13 "hash" 14 "io" 15 16 "github.com/google/tink/go/subtle/random" 17 "golang.org/x/crypto/hkdf" 18 19 "github.com/trustbloc/kms-go/spi/secretlock" 20 21 cipherutil "github.com/trustbloc/kms-go/secretlock/local/internal/cipher" 22 ) 23 24 // package hkdf provides an hkdf implementation of secretlock as a masterlock. 25 // See golang.org/x/crypto/hkdf/hkdf.go for IETF reference. 26 // The IETF RFC in question is RFC 5869. It mentions the following paragraph in the introduction about NIST documents: 27 // " Note that some existing KDF specifications, such as NIST Special 28 // Publication 800-56A [800-56A], NIST Special Publication 800-108 29 // [800-108] and IEEE Standard 1363a-2004 [1363a], either only consider 30 // the second stage (expanding a pseudorandom key), or do not explicitly 31 // differentiate between the "extract" and "expand" stages, often 32 // resulting in design shortcomings. The goal of this specification is 33 // to accommodate a wide range of KDF requirements while minimizing the 34 // assumptions about the underlying hash function. The "extract-then- 35 // expand" paradigm supports well this goal (see [HKDF-paper] for more 36 // information about the design rationale). " 37 38 type masterLockHKDF struct { 39 h func() hash.Hash 40 salt []byte 41 aead cipher.AEAD 42 } 43 44 // NewMasterLock is responsible for encrypting/decrypting with a master key expanded from a passphrase using HKDF 45 // using `passphrase`, hash function `h`, `salt`. 46 // The salt is optional and can be set to nil. 47 // This implementation must not be used directly in Aries framework. It should be passed in 48 // as the second argument to local secret lock service constructor: 49 // `local.NewService(masterKeyReader io.Reader, secLock secretlock.Service)`. 50 func NewMasterLock(passphrase string, h func() hash.Hash, salt []byte) (secretlock.Service, error) { 51 if passphrase == "" { 52 return nil, fmt.Errorf("passphrase is empty") 53 } 54 55 if h == nil { 56 return nil, fmt.Errorf("hash is nil") 57 } 58 59 size := h().Size() 60 if size > sha256.Size { // AEAD cipher requires at most sha256.Size 61 return nil, fmt.Errorf("hash size not supported") 62 } 63 64 // expand an encryption key from passphrase 65 expander := hkdf.New(h, []byte(passphrase), salt, nil) 66 67 masterKey := make([]byte, size) 68 69 _, err := io.ReadFull(expander, masterKey) 70 if err != nil { 71 return nil, err 72 } 73 74 aead, err := cipherutil.CreateAESCipher(masterKey) 75 if err != nil { 76 return nil, err 77 } 78 79 return &masterLockHKDF{ 80 h: h, 81 salt: salt, 82 aead: aead, 83 }, nil 84 } 85 86 // Encrypt a master key in req 87 // 88 // (keyURI is used for remote locks, it is ignored by this implementation) 89 func (m *masterLockHKDF) Encrypt(keyURI string, req *secretlock.EncryptRequest) (*secretlock.EncryptResponse, error) { 90 nonce := random.GetRandomBytes(uint32(m.aead.NonceSize())) 91 ct := m.aead.Seal(nil, nonce, []byte(req.Plaintext), []byte(req.AdditionalAuthenticatedData)) 92 ct = append(nonce, ct...) 93 94 return &secretlock.EncryptResponse{ 95 Ciphertext: base64.URLEncoding.EncodeToString(ct), 96 }, nil 97 } 98 99 // Decrypt a master key in req 100 // (keyURI is used for remote locks, it is ignored by this implementation). 101 func (m *masterLockHKDF) Decrypt(keyURI string, req *secretlock.DecryptRequest) (*secretlock.DecryptResponse, error) { 102 ct, err := base64.URLEncoding.DecodeString(req.Ciphertext) 103 if err != nil { 104 return nil, err 105 } 106 107 nonceSize := uint32(m.aead.NonceSize()) 108 109 // ensure ciphertext contains more than nonce+ciphertext (result from Encrypt()) 110 if len(ct) <= int(nonceSize) { 111 return nil, fmt.Errorf("invalid request") 112 } 113 114 nonce := ct[0:nonceSize] 115 ct = ct[nonceSize:] 116 117 pt, err := m.aead.Open(nil, nonce, ct, []byte(req.AdditionalAuthenticatedData)) 118 if err != nil { 119 return nil, err 120 } 121 122 return &secretlock.DecryptResponse{Plaintext: string(pt)}, nil 123 }