added genpasswd command

This commit is contained in:
Jarek Kowalski
2016-05-21 15:15:38 -07:00
parent c050f93295
commit 3f81d366de
9 changed files with 238 additions and 66 deletions

View File

@@ -30,7 +30,7 @@ test:
vtest:
go test -v -timeout 30s github.com/kopia/kopia/...
doc:
godoc:
godoc -http=:33333
coverage:

View File

@@ -5,21 +5,21 @@
"fmt"
"io"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/vault"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/kopia/kopia/blob"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/vault"
)
var (
createCommand = app.Command("create", "Create new vault and repository.")
createCommandRepository = createCommand.Flag("repository", "Repository path.").Required().String()
createObjectFormat = createCommand.Flag("repo-format", "Format of repository objects.").PlaceHolder("FORMAT").Default("sha256t128-aes256").Enum(supportedObjectFormats()...)
createObjectFormat = createCommand.Flag("repo-format", "Format of repository objects.").PlaceHolder("FORMAT").Default("sha256t160-aes192").Enum(supportedObjectFormats()...)
createMaxBlobSize = createCommand.Flag("max-blob-size", "Maximum size of a data chunk.").PlaceHolder("BYTES").Default("20000000").Int()
createInlineBlobSize = createCommand.Flag("inline-blob-size", "Maximum size of an inline data chunk.").PlaceHolder("BYTES").Default("32768").Int()
createVaultEncryptionFormat = createCommand.Flag("vault-format", "Vault encryption format.").PlaceHolder("FORMAT").Default("aes-256").Enum(supportedVaultEncryptionFormats()...)
createVaultEncryptionFormat = createCommand.Flag("vault-encryption", "Vault encryption.").PlaceHolder("FORMAT").Default("aes-256").Enum(supportedVaultEncryptionFormats()...)
createOverwrite = createCommand.Flag("overwrite", "Overwrite existing data (DANGEROUS).").Bool()
createOnly = createCommand.Flag("create-only", "Create the vault, but don't connect to it.").Short('c').Bool()
)
@@ -100,16 +100,16 @@ func runCreateCommand(context *kingpin.ParseContext) error {
repoFormat.ObjectFormat,
repoFormat.MaxBlobSize)
creds, err := getVaultCredentials(true)
if err != nil {
return fmt.Errorf("unable to get credentials: %v", err)
}
vf, err := vaultFormat()
if err != nil {
return fmt.Errorf("unable to initialize vault format: %v", err)
}
creds, err := getVaultCredentials(true)
if err != nil {
return fmt.Errorf("unable to get credentials: %v", err)
}
fmt.Printf(
"Initializing vault in '%s' with encryption '%v'.\n",
vaultStorage.Configuration().Config.ToURL().String(),
@@ -147,6 +147,7 @@ func supportedVaultEncryptionFormats() []string {
return []string{
"none",
"aes-128",
"aes-192",
"aes-256",
}
}

View File

@@ -0,0 +1,165 @@
package main
import (
"bytes"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"os"
"strings"
"gopkg.in/alecthomas/kingpin.v2"
)
var (
genPasswordCommand = app.Command("genpasswd", "Generate memorable password - inspired by http://xkcd.com/936/")
genPasswordWordsSource = genPasswordCommand.Flag("word-list", "Dictionary words URL or file name").Default("http://kopia.github.io/words/en.txt").String()
genPasswordWordsPerPassword = genPasswordCommand.Flag("words-per-password", "Number of words per password.").Short('w').Default("6").Int()
genPasswordWordSeparator = genPasswordCommand.Flag("words-separator", "Word separator.").Default("-").Short('s').String()
genPasswordUppercase = genPasswordCommand.Flag("uppercase", "Use upper-case versions of words.").Short('u').Bool()
genPasswordCapitalize = genPasswordCommand.Flag("capitalize", "Use capitalized versions of words.").Short('c').Bool()
genPasswordL33t = genPasswordCommand.Flag("l33t", "Use common substitutions o->0, s->$, etc.").Short('l').Bool()
genPasswordMinWordLength = genPasswordCommand.Flag("min-word-length", "Minimum dictionary word length.").Default("4").Int()
genPasswordMaxWordLength = genPasswordCommand.Flag("max-word-length", "Maximum dictionary word length.").Default("8").Int()
genPasswordCount = genPasswordCommand.Flag("num-passwords", "Number of passwords to generate.").Short('n').Default("20").Int()
l33tSubstTable = map[string]string{
"a": "4",
"e": "3",
"l": "1",
"s": "$",
"o": "0",
}
)
func init() {
genPasswordCommand.Action(runGenPassword)
}
func genleet(result *[]string, prefix string, suffix string) {
if len(suffix) == 0 {
return
}
*result = append(*result, prefix+suffix)
ch := suffix[0:1]
genleet(result, prefix+ch, suffix[1:])
if subst, ok := l33tSubstTable[ch]; ok {
genleet(result, prefix+subst, suffix[1:])
}
}
func l33t(w string) []string {
var result []string
genleet(&result, "", w)
return result
}
func getWordList() ([]string, error) {
// Read the words file, that is typically 2-5MB, not a big deal.
allWords, err := ioutil.ReadFile(*genPasswordWordsSource)
if err != nil {
if os.IsNotExist(err) {
// File not found, try URL.
fmt.Printf("Downloading word list from %v ...\n", *genPasswordWordsSource)
resp, err := http.Get(*genPasswordWordsSource)
if err != nil {
return nil, err
}
defer resp.Body.Close()
allWords, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
} else {
return nil, err
}
} else {
fmt.Printf("Using word list from file: %v\n", *genPasswordWordsSource)
}
words := bytes.Split(allWords, []byte("\n"))
var usableWords []string
for _, w := range words {
word := strings.TrimSpace(string(w))
if len(word) >= *genPasswordMinWordLength && len(word) <= *genPasswordMaxWordLength {
usableWords = append(usableWords, word)
if *genPasswordUppercase {
usableWords = append(usableWords, strings.ToUpper(word))
}
if *genPasswordCapitalize {
usableWords = append(usableWords, strings.ToUpper(word[0:1])+word[1:])
}
if *genPasswordL33t {
usableWords = append(usableWords, l33t(word)...)
}
}
}
if len(usableWords) < 500 {
return nil, fmt.Errorf("word list too short: %v entries", len(usableWords))
}
fmt.Printf("Got %v usable words.\n", len(usableWords))
fmt.Printf("\n")
return usableWords, nil
}
func runGenPassword(context *kingpin.ParseContext) error {
usableWords, err := getWordList()
if err != nil {
return fmt.Errorf("unable to read word list: %v", err)
}
randomBitsPerWord := math.Log2(float64(len(usableWords)))
randomBitsPerSeparator := math.Log2(float64(len(*genPasswordWordSeparator)))
wordCharSetSize := 26
if *genPasswordUppercase || *genPasswordCapitalize {
wordCharSetSize *= 2
}
fmt.Printf("Memorable passwords, inspired by http://xkcd.com/936/ :\n")
fmt.Printf("\n")
for i := 0; i < *genPasswordCount; i++ {
pass := generatePasswordFromWords(usableWords)
fmt.Printf("%2d. %-60v blind entropy %.2f bits\n", i+1, pass, math.Log2(math.Pow(float64(wordCharSetSize), float64(len(pass)))))
}
fmt.Printf("\n")
fmt.Printf("Password entropy with full knowledge of the algorithm: %.2f bits.\n",
randomBitsPerWord*float64(*genPasswordWordsPerPassword)+
randomBitsPerSeparator*float64(len(*genPasswordWordSeparator)))
return nil
}
func secureRandomInt() int {
var b [4]byte
io.ReadFull(rand.Reader, b[:])
return ((int(b[0]) & 0x7F) << 24) | (int(b[1]) << 16) | (int(b[2]) << 8) | int(b[3])
}
func generatePasswordFromWords(words []string) string {
var result string
separators := *genPasswordWordSeparator
for i := 0; i < *genPasswordWordsPerPassword; i++ {
if i > 0 {
x := secureRandomInt() % len(separators)
result += separators[x : x+1]
}
result += words[secureRandomInt()%len(words)]
}
return result
}

View File

@@ -10,10 +10,9 @@
"strings"
"github.com/kopia/kopia/blob"
"github.com/kopia/kopia/vault"
"golang.org/x/crypto/ssh/terminal"
"github.com/kopia/kopia/vault"
)
var (

View File

@@ -1,21 +1,10 @@
/*
The 'kopia' utility support screating and accessing backups from command line.
The 'kopia' utility supports creating and accessing backups from command line.
Usage:
$ kopia [<flags>] <subcommand> [<args> ...]
Common subcommands:
init <provider> [<args> ...]
Connects to the backup repo.
backup [<flags>] <directory>...
Copies local directory to backup repository.
mount --objectID=CHUNKID <mountpoint>
Mounts remote backup as local directory.
Use 'kopia help' to see more details.
*/
package main

View File

@@ -13,7 +13,7 @@ You can download pre-built `kopia` binary from http://kopia.github.io/download.
### Installation From Source
To build Kopia from source you need to have the latest version of [Go](https://golang.org/dl/) installed and run the following commands:
To build Kopia from source you need the latest version of [Go](https://golang.org/dl/) and run the following commands:
```
mkdir $HOME/kopia
@@ -22,7 +22,7 @@ go get github.com/kopia/kopia
go install github.com/kopia/kopia/cmd/kopia
```
This will automatically download and build kopia and put the resulting binary in `$HOME/kopia/bin`. For convenience it's best to add this directory to system `PATH` or copy it to a directory already in the path, such as `/usr/local/bin`.
This will automatically download and build kopia and put the resulting binary in `$HOME/kopia/bin`. For convenience it's best to add this directory to system `PATH` or copy/symlink it to a directory already in the path, such as `/usr/local/bin`.
## Getting Started

View File

@@ -32,6 +32,7 @@ func (mkc *masterKeyCredentials) getMasterKey(salt []byte) []byte {
return mkc.key
}
// MasterKey returns master key-based Credentials with the specified key.
func MasterKey(key []byte) (Credentials, error) {
if len(key) < MinMasterKeyLength {
return nil, fmt.Errorf("master key too short")
@@ -48,6 +49,7 @@ func (pc *passwordCredentials) getMasterKey(salt []byte) []byte {
return pbkdf2.Key([]byte(pc.password), salt, pbkdf2Rounds, passwordBasedKeySize, sha256.New)
}
// Password returns password-based Credentials with the specified password.
func Password(password string) (Credentials, error) {
if len(password) < MinPasswordLength {
return nil, fmt.Errorf("password too short")

View File

@@ -44,63 +44,73 @@ func (v *Vault) writeEncryptedBlock(name string, content []byte) error {
return err
}
hash, err := v.newChecksum()
if err != nil {
return err
if blk != nil {
hash, err := v.newChecksum()
if err != nil {
return err
}
ivLength := blk.BlockSize()
ivPlusContentLength := ivLength + len(content)
cipherText := make([]byte, ivPlusContentLength+hash.Size())
// Store IV at the beginning of ciphertext.
iv := cipherText[0:ivLength]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return err
}
ctr := cipher.NewCTR(blk, iv)
ctr.XORKeyStream(cipherText[ivLength:], content)
hash.Write(cipherText[0:ivPlusContentLength])
copy(cipherText[ivPlusContentLength:], hash.Sum(nil))
content = cipherText
}
ivLength := blk.BlockSize()
ivPlusContentLength := ivLength + len(content)
cipherText := make([]byte, ivPlusContentLength+hash.Size())
// Store IV at the beginning of ciphertext.
iv := cipherText[0:ivLength]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return err
}
ctr := cipher.NewCTR(blk, iv)
ctr.XORKeyStream(cipherText[ivLength:], content)
hash.Write(cipherText[0:ivPlusContentLength])
copy(cipherText[ivPlusContentLength:], hash.Sum(nil))
return v.storage.PutBlock(name, ioutil.NopCloser(bytes.NewBuffer(cipherText)), blob.PutOptions{
return v.storage.PutBlock(name, ioutil.NopCloser(bytes.NewBuffer(content)), blob.PutOptions{
Overwrite: true,
})
}
func (v *Vault) readEncryptedBlock(name string) ([]byte, error) {
cipherText, err := v.storage.GetBlock(name)
content, err := v.storage.GetBlock(name)
if err != nil {
return nil, err
}
hash, err := v.newChecksum()
if err != nil {
return nil, err
}
p := len(cipherText) - hash.Size()
hash.Write(cipherText[0:p])
expectedChecksum := hash.Sum(nil)
actualChecksum := cipherText[p:]
if !hmac.Equal(expectedChecksum, actualChecksum) {
return nil, fmt.Errorf("cannot read encrypted block: incorrect checksum")
}
blk, err := v.newCipher()
if err != nil {
return nil, err
}
ivLength := blk.BlockSize()
if blk != nil {
plainText := make([]byte, len(cipherText)-ivLength-hash.Size())
iv := cipherText[0:blk.BlockSize()]
hash, err := v.newChecksum()
if err != nil {
return nil, err
}
ctr := cipher.NewCTR(blk, iv)
ctr.XORKeyStream(plainText, cipherText[ivLength:len(cipherText)-hash.Size()])
return plainText, nil
p := len(content) - hash.Size()
hash.Write(content[0:p])
expectedChecksum := hash.Sum(nil)
actualChecksum := content[p:]
if !hmac.Equal(expectedChecksum, actualChecksum) {
return nil, fmt.Errorf("cannot read encrypted block: incorrect checksum")
}
ivLength := blk.BlockSize()
plainText := make([]byte, len(content)-ivLength-hash.Size())
iv := content[0:blk.BlockSize()]
ctr := cipher.NewCTR(blk, iv)
ctr.XORKeyStream(plainText, content[ivLength:len(content)-hash.Size()])
content = plainText
}
return content, nil
}
func (v *Vault) newChecksum() (hash.Hash, error) {
@@ -118,6 +128,8 @@ func (v *Vault) newChecksum() (hash.Hash, error) {
func (v *Vault) newCipher() (cipher.Block, error) {
switch v.format.Encryption {
case "none":
return nil, nil
case "aes-128":
k := make([]byte, 16)
v.deriveKey(purposeAESKey, k)
@@ -181,6 +193,7 @@ func (v *Vault) OpenRepository() (repo.Repository, error) {
return repo.NewRepository(storage, rc.Format)
}
// Get deserializes JSON data stored in the vault into the specified content structure.
func (v *Vault) Get(id string, content interface{}) error {
j, err := v.readEncryptedBlock(id)
if err != nil {
@@ -190,6 +203,7 @@ func (v *Vault) Get(id string, content interface{}) error {
return json.Unmarshal(j, content)
}
// Put stores the contents of an item stored in a vault with a given ID.
func (v *Vault) Put(id string, content interface{}) error {
j, err := json.Marshal(content)
if err != nil {

View File

@@ -10,6 +10,7 @@
"github.com/kopia/kopia/repo"
)
// Manager exposes FUSE filesystem nodes based on repository entries.
type Manager interface {
NewNodeFromEntry(e *fs.Entry) fusefs.Node
}
@@ -50,6 +51,7 @@ func (mgr *manager) open(oid repo.ObjectID) (io.ReadSeeker, error) {
return mgr.repo.Open(oid)
}
// NewManager returns new vfs.Manager that
func NewManager(repo repo.Repository) Manager {
return &manager{
repo: repo,