// MarshalSecretStore serialises and encrypts the data store to a byte // slice suitable for writing to disk. func MarshalSecretStore(s *SecretStore, m secret.ScryptMode) ([]byte, bool) { if !s.Valid() { return nil, false } out, err := json.Marshal(s) if err != nil { return nil, false } defer util.Zero(out) salt := util.RandBytes(saltSize) if salt == nil { return nil, false } key := secret.DeriveKeyStrength(s.passphrase, salt, m) if key == nil { return nil, false } defer util.Zero(key[:]) enc, ok := secret.Encrypt(key, out) if !ok { return nil, false } defer s.Zero() enc = append(salt, enc...) return enc, true }
// NewSession sets up a new session. The Last field should be sent // to the client. The returned public key should be sent to the // user for generating a shared MAC key. The authenticator should ensure // some mechanism for expiring sessions exists. func NewSession(pub []byte) (*Authenticator, []byte, error) { next := util.RandBytes(sessionLength) if next == nil { return nil, nil, errors.New("auth: PRNG failure") } ephemeral, err := public.GenerateKey() if err != nil || !ephemeral.Valid() { return nil, nil, errors.New("auth: failed to set up session key") } // Validated that the key was correct previously. ephemeralPublic, _ := public.MarshalPublic(ephemeral.PublicKey) peer, err := public.UnmarshalPublic(pub) if err != nil { return nil, nil, err } shared := public.KeyExchange(ephemeral, peer) return &Authenticator{ Type: TypeSession, Last: hex.EncodeToString(next), Secret: shared, }, ephemeralPublic, nil }
// GenerateKey returns a randomly generated secretbox key. Typically, // you should use DeriveKey to get a key from a passphrase // instead. Returns nil on failure. func GenerateKey() *[KeySize]byte { var key [KeySize]byte rb := util.RandBytes(KeySize) if rb == nil || len(rb) != KeySize { return nil } defer util.Zero(rb) copy(key[:], rb) return &key }
func TestDeriveKey(t *testing.T) { salt := util.RandBytes(SaltSize) if k := DeriveKey(password, salt); k == nil { t.Fatal("secret: failed to derive key") } n := scryptParams.N scryptParams.N = 0 if k := DeriveKey(password, salt); k != nil { t.Fatal("secret: should fail to derive key with invalid Scrypt params") } scryptParams.N = n }
// NewGoogleTOTP generates a new Google-authenticator standard TOTP // token. func NewGoogleTOTP(label string) (*Authenticator, *UserTOTP, error) { key := util.RandBytes(sha1.Size) if key == nil { return nil, nil, errors.New("auth: PRNG failure") } auth, _ := ImportGoogleTOTP(key) ud, err := ExportUserTOTP(auth, label) if err != nil { return nil, nil, err } return auth, ud, nil }
func TestUnlockKeyFail(t *testing.T) { var password = []byte("password") var message = []byte("this is not a valid private key") salt := util.RandBytes(saltSize) key := secret.DeriveKey(password, salt) out, ok := secret.Encrypt(key, message) if !ok { t.Fatal("public: encrypt failed") } out = append(salt, out...) _, ok = UnlockKey(out, password) if ok { t.Fatal("public: unlock key should fail with invalid private key") } }
func TestLockKey(t *testing.T) { key, err := GenerateKey() if err != nil { t.Fatalf("Failed to generate test key: %v", err) } out, ok := EncryptAndSign(testKey, key.PublicKey, message) if !ok { t.Fatal("Failed to encrypt and sign message.") } locked, ok := LockKey(key, []byte("this is my password")) if !ok { t.Fatal("Failed to lock key.") } _, ok = UnlockKey(locked, []byte("this is my password.")) if ok { t.Fatal("Unlocked with wrong passphrase.") } priv, ok := UnlockKey(locked, []byte("this is my password")) if !ok { t.Fatal("Unlocking key failed.") } recovered, ok := DecryptAndVerify(priv, testKey.PublicKey, out) if !ok { t.Fatal("Failed to decrypt and verify message.") } if !bytes.Equal(message, recovered) { t.Fatalf("Corrupt message.\nRecovered: %x\nMessage: %x\n", recovered, message) } salt := util.RandBytes(saltSize) buf := bytes.NewBuffer(salt) util.SetPRNG(buf) _, ok = LockKey(priv, []byte("password")) if ok { t.Fatal("public: expect locking to fail with bad PRNG") } util.SetPRNG(rand.Reader) }
// EncryptFile securely stores the encoded blob under the filename. func EncryptFile(filename string, passphrase, encoded []byte) (err error) { salt := util.RandBytes(SaltSize) if salt == nil { err = errors.New("password: failed to generate new salt") return } defer util.Zero(encoded) key := DeriveKey(passphrase, salt) data, ok := Encrypt(key, encoded) if !ok { data = nil err = errors.New("password: failed to encrypt data") return } data = append(salt, data...) err = ioutil.WriteFile(filename, data, 0600) return }
func TestDeriveKey(t *testing.T) { salt := util.RandBytes(SaltSize) if k := DeriveKey(password, salt); k == nil { t.Fatal("secret: failed to derive key") } if k := DeriveKey(password, salt); k == nil { t.Fatal("secret: key derivation failure") } if k := DeriveKeyStrength(password, salt, ScryptInteractive); k == nil { t.Fatal("secret: key derivation failure") } scryptMode[-1] = scryptParams{0, 0, 0} if k := DeriveKeyStrength(password, salt, -1); k != nil { t.Fatal("secret: should fail to derive key with invalid Scrypt parameters") } delete(scryptMode, -1) }
func TestEncryptFile(t *testing.T) { defer os.Remove(testEncryptedFile) err := EncryptFile(testEncryptedFile, password, dup(message)) if err != nil { t.Fatalf("%v", err) } out, err := DecryptFile(testEncryptedFile, password) if err != nil { t.Fatalf("%v", err) } if !bytes.Equal(out, message) { t.Fatal("secret: decrypted file doesn't match original message") } if _, err = DecryptFile(testNoSuchFile, password); err == nil { t.Fatal("secret: decrypt file should fail with IO error") } if _, err = DecryptFile(testUnencryptedFile, password); err == nil { t.Fatal("secret: decrypt file should fail with unencrypted file") } salt := util.RandBytes(SaltSize) buf := &bytes.Buffer{} util.SetPRNG(buf) err = EncryptFile(testEncryptedFile, password, dup(message)) if err == nil { t.Fatal("secret: encrypt file should fail with bad PRNG") } buf.Write(salt) err = EncryptFile(testEncryptedFile, password, dup(message)) if err == nil { t.Fatal("secret: encrypt file should fail with bad PRNG") } util.SetPRNG(rand.Reader) }
// ValidateSession ensures that the OTP provided contains the next // value and the appropriate HMAC for the session. func ValidateSession(auth *Authenticator, otp string) (bool, error) { if (auth == nil) || (auth.Type != TypeSession) { return false, ErrInvalidAuthenticator } otpBytes, err := hex.DecodeString(otp) if err != nil { return false, err } if len(otpBytes) != 2*sessionLength { return false, ErrValidationFail } lastBytes, err := hex.DecodeString(auth.Last) if err != nil { return false, err } h := hmac.New(sha256.New, auth.Secret) h.Write(lastBytes) expected := h.Sum(nil) if !bytes.Equal(otpBytes[:sessionLength], lastBytes) { return false, ErrValidationFail } if !hmac.Equal(otpBytes[sessionLength:], expected) { return false, ErrValidationFail } next := util.RandBytes(sessionLength) if next == nil { return false, errors.New("auth: PRNG failure") } auth.Last = hex.EncodeToString(next) return true, nil }
// LockKey secures the private key with the passphrase, using Scrypt // and NaCl's secretbox. func LockKey(priv *PrivateKey, passphrase []byte) ([]byte, bool) { out, err := MarshalPrivate(priv) if err != nil { return nil, false } defer util.Zero(out) salt := util.RandBytes(saltSize) if salt == nil { return nil, false } key := secret.DeriveKey(passphrase, salt) defer util.Zero(key[:]) out, ok := secret.Encrypt(key, out) if !ok { return nil, false } out = append(salt, out...) return out, true }
func main() { flArmour := flag.Bool("a", false, "armour output") flOutDir := flag.String("o", ".", "output directory") flOutfile := flag.String("f", "passcrypt.enc", "pack file") flShowManifest := flag.Bool("l", false, "list the files in the archive") flUnpack := flag.Bool("u", false, "unpack the archive") flag.BoolVar(&verbose, "v", false, "verbose mode") flVersion := flag.Bool("V", false, "display version and exit") flag.Parse() if *flVersion { fmt.Println("passcrypt version", util.VersionString()) os.Exit(0) } if *flUnpack || *flShowManifest { if flag.NArg() != 1 { util.Errorf("Only one file may be unpacked at a time.\n") os.Exit(1) } in, err := ioutil.ReadFile(flag.Arg(0)) if err != nil { util.Errorf("%v\n", err) os.Exit(1) } if p, _ := pem.Decode(in); p != nil { if p.Type != header { util.Errorf("Wrong header for archive.\n") os.Exit(1) } in = p.Bytes } if len(in) <= saltLength { util.Errorf("Invalid archive.\n") os.Exit(1) } salt := in[:saltLength] in = in[saltLength:] passphrase, err := readpass.PasswordPromptBytes("Password: "******"%v\n", err) os.Exit(1) } key := secret.DeriveKey(passphrase, salt) if key == nil { util.Errorf("Failed to derive key.n\n") os.Exit(1) } in, ok := secret.Decrypt(key, in) if !ok { util.Errorf("Decryption failed.\n") os.Exit(1) } defer util.Zero(in) if *flUnpack { err = unpackFiles(in, *flOutDir) if err != nil { util.Errorf("%v\n", err) os.Exit(1) } } else if *flShowManifest { var files []File _, err := asn1.Unmarshal(in, &files) if err != nil { util.Errorf("%v\n", err) os.Exit(1) } fmt.Println("Manifest for", flag.Arg(0)) fmt.Printf("\n") for _, file := range files { fmt.Printf("\t%s", file.Path) if os.FileMode(file.Mode).IsDir() { fmt.Printf("/") } fmt.Printf("\n") } } return } if flag.NArg() == 0 { return } passphrase, err := readpass.PasswordPromptBytes("Password: "******"%v\n", err) os.Exit(1) } salt := util.RandBytes(saltLength) if salt == nil { util.Errorf("Failed to generate a random salt.\n") os.Exit(1) } key := secret.DeriveKey(passphrase, salt) if key == nil { util.Errorf("Failed to derive key.n\n") os.Exit(1) } out, err := packFiles(flag.Args()) if err != nil { util.Errorf("%v\n", err) os.Exit(1) } var ok bool out, ok = secret.Encrypt(key, out) if !ok { util.Errorf("Encryption failed.\n") os.Exit(1) } out = append(salt, out...) if *flArmour { p := &pem.Block{ Type: header, Bytes: out, } out = pem.EncodeToMemory(p) } err = ioutil.WriteFile(*flOutfile, out, 0644) if err != nil { util.Errorf("%v\n", err) os.Exit(1) } }