github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/lnutil/keyfile.go (about) 1 package lnutil 2 3 import ( 4 "crypto/rand" 5 "encoding/hex" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "strings" 10 11 "github.com/mit-dci/lit/logging" 12 13 "github.com/howeyc/gopass" 14 "golang.org/x/crypto/nacl/secretbox" 15 "golang.org/x/crypto/scrypt" 16 ) 17 18 /* should switch to the codahale/chacha20poly1305 package. Or whatever we're 19 using for LNDC; otherwise it's 2 almost-the-same stream ciphers to import */ 20 21 // warning! look at those imports! crypto! hopefully this works! 22 23 /* on-disk stored keys are 32bytes. This is good for ed25519 private keys, 24 for seeds for bip32, for individual secp256k1 priv keys, and so on. 25 32 bytes is enough for anyone. 26 If you want fewer bytes, put some zeroes at the end */ 27 28 // LoadKeyFromFileInteractive opens the file 'filename' and presents a 29 // keyboard prompt for the passphrase to decrypt it. It returns the 30 // key if decryption works, or errors out. 31 func LoadKeyFromFileInteractive(filename string) (*[32]byte, error) { 32 a, err := os.Stat(filename) 33 if err != nil { 34 return nil, err 35 } 36 if a.Size() < 80 { // there can't be a password... 37 return LoadKeyFromFileArg(filename, nil) 38 } 39 fmt.Printf("passphrase: ") 40 pass, err := gopass.GetPasswd() 41 if err != nil { 42 return nil, err 43 } 44 fmt.Printf("\n") 45 return LoadKeyFromFileArg(filename, pass) 46 } 47 48 // LoadKeyFromFileArg opens the file and returns the key. If the key is 49 // unencrypted it will ignore the password argument. 50 func LoadKeyFromFileArg(filename string, pass []byte) (*[32]byte, error) { 51 priv32 := new([32]byte) 52 keyhex, err := ioutil.ReadFile(filename) 53 if err != nil { 54 return priv32, err 55 } 56 keyhex = []byte(strings.TrimSpace(string(keyhex))) 57 enckey, err := hex.DecodeString(string(keyhex)) 58 if err != nil { 59 return priv32, err 60 } 61 62 if len(enckey) == 32 { // UNencrypted key, length 32 63 v, p := os.LookupEnv("LIT_KEYFILE_WARN") 64 if !p || (p && v != "0") { 65 logging.Warnf("WARNING!! Key file not encrypted!!\n") 66 logging.Warnf("Anyone who can read the key file can take everything!\n") 67 logging.Warnf("You should start over and use a good passphrase!\n") 68 } 69 70 copy(priv32[:], enckey[:]) 71 return priv32, nil 72 } 73 // enckey should be 72 bytes. 24 for scrypt salt/box nonce, 74 // 16 for box auth 75 if len(enckey) != 72 { 76 return priv32, fmt.Errorf("Key length error for %s ", filename) 77 } 78 // enckey is actually encrypted, get derived key from pass and salt 79 // first extract salt 80 salt := new([24]byte) // salt (also nonce for secretbox) 81 dk32 := new([32]byte) // derived key array 82 copy(salt[:], enckey[:24]) // first 24 bytes are scrypt salt/box nonce 83 84 dk, err := scrypt.Key(pass, salt[:], 16384, 8, 1, 32) // derive key 85 if err != nil { 86 return priv32, err 87 } 88 copy(dk32[:], dk[:]) // copy into fixed size array 89 90 // nonce for secretbox is the same as scrypt salt. Seems fine. Really. 91 priv, worked := secretbox.Open(nil, enckey[24:], salt, dk32) 92 if worked != true { 93 return priv32, fmt.Errorf("Decryption failed for %s ", filename) 94 } 95 copy(priv32[:], priv[:]) //copy decrypted private key into array 96 97 priv = nil // this probably doesn't do anything but... eh why not 98 return priv32, nil 99 } 100 101 // saves a 32 byte key to file, prompting for passphrase. 102 // if user enters empty passphrase (hits enter twice), will be saved 103 // in the clear. 104 func SaveKeyToFileInteractive(filename string, priv32 *[32]byte) error { 105 var match bool 106 var err error 107 var pass1, pass2 []byte 108 for match != true { 109 fmt.Printf("passphrase: ") 110 pass1, err = gopass.GetPasswd() 111 if err != nil { 112 return err 113 } 114 fmt.Printf("repeat passphrase: ") 115 pass2, err = gopass.GetPasswd() 116 if err != nil { 117 return err 118 } 119 if string(pass1) == string(pass2) { 120 match = true 121 } else { 122 fmt.Printf("user input error. Try again gl hf dd.\n") 123 } 124 } 125 fmt.Printf("\n") 126 return SaveKeyToFileArg(filename, priv32, pass1) 127 } 128 129 // saves a 32 byte key to a file, encrypting with pass. 130 // if pass is nil or zero length, doesn't encrypt and just saves in hex. 131 func SaveKeyToFileArg(filename string, priv32 *[32]byte, pass []byte) error { 132 if len(pass) == 0 { // zero-length pass, save unencrypted 133 keyhex := fmt.Sprintf("%x\n", priv32[:]) 134 err := ioutil.WriteFile(filename, []byte(keyhex), 0600) 135 if err != nil { 136 return err 137 } 138 logging.Warnf("WARNING!! Key file not encrypted!!\n") 139 logging.Warnf("Anyone who can read the key file can take everything!\n") 140 logging.Warnf("You should start over and use a good passphrase!\n") 141 logging.Warnf("Saved unencrypted key at %s\n", filename) 142 return nil 143 } 144 145 salt := new([24]byte) // salt for scrypt / nonce for secretbox 146 dk32 := new([32]byte) // derived key from scrypt 147 148 //get 24 random bytes for scrypt salt (and secretbox nonce) 149 _, err := rand.Read(salt[:]) 150 if err != nil { 151 return err 152 } 153 // next use the pass and salt to make a 32-byte derived key 154 dk, err := scrypt.Key(pass, salt[:], 16384, 8, 1, 32) 155 if err != nil { 156 return err 157 } 158 copy(dk32[:], dk[:]) 159 160 enckey := append(salt[:], secretbox.Seal(nil, priv32[:], salt, dk32)...) 161 // enckey = append(salt, enckey...) 162 keyhex := fmt.Sprintf("%x\n", enckey) 163 164 err = ioutil.WriteFile(filename, []byte(keyhex), 0600) 165 if err != nil { 166 return err 167 } 168 logging.Infof("Wrote encrypted key to %s\n", filename) 169 return nil 170 } 171 172 // ReadKeyFile returns an 32 byte key from a file. 173 // If there's no file there, it'll make one. If there's a password needed, 174 // it'll prompt for one. One stop function. 175 func ReadKeyFile(filename string) (*[32]byte, error) { 176 key32 := new([32]byte) 177 _, err := os.Stat(filename) 178 if err != nil { 179 if os.IsNotExist(err) { 180 // no key found, generate and save one 181 logging.Infof("No file %s, generating.\n", filename) 182 183 _, err := rand.Read(key32[:]) 184 if err != nil { 185 return nil, err 186 } 187 188 err = SaveKeyToFileInteractive(filename, key32) 189 if err != nil { 190 return nil, err 191 } 192 } else { 193 // unknown error, crash 194 logging.Errorf("unknown error reading keyfile\n") 195 return nil, err 196 } 197 } 198 return LoadKeyFromFileInteractive(filename) 199 }