package vault import ( "bytes" "crypto/aes" "crypto/sha256" "encoding/hex" "fmt" "strings" "golang.org/x/crypto/bcrypt" ) // Tunable params for newly generated passwords. var ( // bcrypt hash cost. The higher, the longer it takes to derive a password. // Helps protect the hashes against brute force attacks. HashCost = 14 ) // GenerateHash generates a bcrypt hash (slow) of a password, and then returns // a SHA-256 sum of the hash. func GenerateHash(password string) ([]byte, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), HashCost) if err != nil { return nil, err } // Make it a SHA-256 hash for easy use with AES-256. hasher := sha256.New() _, err = hasher.Write(hash) bin := hasher.Sum(nil) return bin, err } // HashToFilename converts a bcrypt hash into a safe filename on disk. // // It takes a SHA-256 sum of the hash and then splits it into a short tree // for filesystem efficiency. func HashToFilename(hash []byte) string { sha := hex.EncodeToString(hash) // Split it into a path tree. var ( p1 = sha[:2] p2 = sha[2:20] p3 = sha[20:40] p4 = sha[40:] ) filename := strings.Join([]string{p1, p2, p3, p4}, "/") return filename } // Encrypt data using AES-256. func Encrypt(key []byte, data []byte) ([]byte, error) { // Initialize an AES cipher. encryptor, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("Encrypt: aes.NewCipher error: %s", err) } // Pad the plaintext until it's a multiple of the AES block size. buf := bytes.NewBuffer(data) for buf.Len()%aes.BlockSize != 0 { buf.WriteByte(' ') } Log.Info("data: %+v", data) Log.Info("buf: %+v", buf.Bytes()) // Encode the data to ciphertext. ciphertext := make([]byte, aes.BlockSize+buf.Len()) encryptor.Encrypt(ciphertext, buf.Bytes()) fmt.Printf("cipher: %+v\n", ciphertext) return ciphertext, nil }