Skip to content

Commit 691705a

Browse files
author
Rijul Gulati
committed
Initial commit
1 parent b12d5a7 commit 691705a

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

andotp/andotp.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package andotp
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/sha1"
7+
"encoding/binary"
8+
"fmt"
9+
"io/ioutil"
10+
"math/rand"
11+
12+
"golang.org/x/crypto/pbkdf2"
13+
)
14+
15+
const (
16+
IV_LEN int = 12
17+
KEY_LEN int = 32
18+
ITERATION_LEN int = 4
19+
SALT_LEN int = 12
20+
MAX_ITERATIONS int = 160000
21+
MIN_ITERATIONS int = 140000
22+
)
23+
24+
func Encrypt(plaintext []byte, password string) ([]byte, error) {
25+
26+
var finalCipher []byte
27+
iter := make([]byte, ITERATION_LEN)
28+
iv := make([]byte, IV_LEN)
29+
salt := make([]byte, SALT_LEN)
30+
31+
iterations := rand.Intn(MAX_ITERATIONS-MIN_ITERATIONS) + MIN_ITERATIONS
32+
binary.BigEndian.PutUint32(iter, uint32(iterations))
33+
34+
_, err := rand.Read(iv)
35+
if err != nil {
36+
return nil, FormatError(err.Error())
37+
}
38+
39+
_, err = rand.Read(salt)
40+
if err != nil {
41+
return nil, FormatError(err.Error())
42+
}
43+
44+
secretKey := pbkdf2.Key([]byte(password), salt, iterations, KEY_LEN, sha1.New)
45+
46+
block, err := aes.NewCipher(secretKey)
47+
if err != nil {
48+
return nil, FormatError(err.Error())
49+
}
50+
51+
aesgcm, err := cipher.NewGCM(block)
52+
if err != nil {
53+
return nil, FormatError(err.Error())
54+
}
55+
56+
cipherText := aesgcm.Seal(nil, iv, plaintext, nil)
57+
58+
finalCipher = append(finalCipher, iter...)
59+
finalCipher = append(finalCipher, salt...)
60+
finalCipher = append(finalCipher, iv...)
61+
finalCipher = append(finalCipher, cipherText...)
62+
63+
return finalCipher, nil
64+
65+
}
66+
67+
func Decrypt(encryptedtext []byte, password string) ([]byte, error) {
68+
69+
iterations := encryptedtext[:ITERATION_LEN]
70+
salt := encryptedtext[ITERATION_LEN : ITERATION_LEN+SALT_LEN]
71+
iv := encryptedtext[ITERATION_LEN+SALT_LEN : ITERATION_LEN+SALT_LEN+IV_LEN]
72+
cipherText := encryptedtext[ITERATION_LEN+SALT_LEN+IV_LEN:]
73+
iter := int(binary.BigEndian.Uint32(iterations))
74+
secretKey := pbkdf2.Key([]byte(password), salt, iter, KEY_LEN, sha1.New)
75+
76+
block, err := aes.NewCipher(secretKey)
77+
if err != nil {
78+
return nil, FormatError(err.Error())
79+
}
80+
81+
aesgcm, err := cipher.NewGCM(block)
82+
if err != nil {
83+
return nil, FormatError(err.Error())
84+
}
85+
86+
plaintextbytes, err := aesgcm.Open(nil, iv, cipherText, nil)
87+
if err != nil {
88+
return nil, FormatError(err.Error())
89+
}
90+
91+
return plaintextbytes, nil
92+
}
93+
94+
func FormatError(e string) error {
95+
return fmt.Errorf("error: %s", e)
96+
}
97+
98+
func ReadFile(file string) ([]byte, error) {
99+
return ioutil.ReadFile(file)
100+
}

andotp/andotp_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package andotp_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/grijul/go-andotp/andotp"
7+
)
8+
9+
const (
10+
jsonstr string = "[{\"secret\":\"SOMEAWESOMESECRET\",\"issuer\":\"SOMEAWESOMEISSUER\",\"label\":\"TESTLABEL\",\"digits\":6,\"type\":\"TOTP\",\"algorithm\":\"SHA1\",\"thumbnail\":\"Default\",\"last_used\":1000000000000,\"used_frequency\":0,\"period\":30,\"tags\":[]}]"
11+
password string = "testpass"
12+
)
13+
14+
func TestEncryptDecrypt(t *testing.T) {
15+
16+
encryptedtext, err := andotp.Encrypt([]byte(jsonstr), password)
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
21+
decryptedtext, err := andotp.Decrypt(encryptedtext, password)
22+
if err != nil {
23+
t.Error(err)
24+
}
25+
26+
if string(decryptedtext) != jsonstr {
27+
t.Error("Encryption/Decryption failed. Text mismatch")
28+
}
29+
}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/grijul/go-andotp
2+
3+
go 1.16
4+
5+
require (
6+
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
7+
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
8+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
2+
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
3+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
4+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
5+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
7+
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
8+
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
9+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
10+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

main.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"syscall"
9+
10+
"github.com/grijul/go-andotp/andotp"
11+
"golang.org/x/term"
12+
)
13+
14+
func main() {
15+
16+
flag.Usage = func() {
17+
fmt.Printf("Usage: %s -i <INPUT_FILE> {-e|-d} [-o <OUT_FILE>] [-p PASSWORD]\n\n", os.Args[0])
18+
flag.PrintDefaults()
19+
}
20+
21+
encryptPtr := flag.Bool("e", false, "Encrypt file.")
22+
decryptPtr := flag.Bool("d", false, "Decrypt file")
23+
inputFilePtr := flag.String("i", "", "Input File")
24+
passwordPtr := flag.String("p", "", "Encryption Password. This option can be skipped to get password prompt.")
25+
outputFilePtr := flag.String("o", "", "Output File. If no file is provided, output is printed to STDOUT")
26+
27+
flag.Parse()
28+
29+
if *inputFilePtr == "" {
30+
fmt.Println(andotp.FormatError("No input file provided\nSee -h for available options."))
31+
os.Exit(0)
32+
}
33+
34+
if *encryptPtr && *decryptPtr {
35+
fmt.Println(andotp.FormatError("Please provide any one of encrypt (-e) or decrypt (-d) flag"))
36+
os.Exit(0)
37+
}
38+
39+
if *passwordPtr == "" {
40+
*passwordPtr = getPassword()
41+
if *passwordPtr == "" {
42+
fmt.Println(andotp.FormatError("No password provided."))
43+
os.Exit(0)
44+
}
45+
}
46+
47+
if *encryptPtr {
48+
49+
plaintext, err := andotp.ReadFile(*inputFilePtr)
50+
if err != nil {
51+
fmt.Print(err.Error())
52+
os.Exit(0)
53+
}
54+
55+
filecontent, err := andotp.Encrypt(plaintext, *passwordPtr)
56+
57+
if err != nil {
58+
fmt.Print(err.Error())
59+
os.Exit(0)
60+
}
61+
62+
processfile(filecontent, *outputFilePtr)
63+
64+
} else if *decryptPtr {
65+
66+
encryptedtext, err := andotp.ReadFile(*inputFilePtr)
67+
if err != nil {
68+
fmt.Print(err.Error())
69+
os.Exit(0)
70+
}
71+
72+
filecontent, err := andotp.Decrypt(encryptedtext, *passwordPtr)
73+
74+
if err != nil {
75+
fmt.Print(err.Error())
76+
os.Exit(0)
77+
}
78+
79+
processfile(filecontent, *outputFilePtr)
80+
81+
} else {
82+
fmt.Println(andotp.FormatError("Please provide encrypt (-e) or decrypt (-d) flag"))
83+
os.Exit(0)
84+
}
85+
}
86+
87+
func processfile(filecontent []byte, outputfile string) {
88+
if outputfile == "" {
89+
fmt.Printf("%s", filecontent)
90+
} else {
91+
ioutil.WriteFile(outputfile, filecontent, 0644)
92+
}
93+
}
94+
95+
func getPassword() string {
96+
fmt.Print("Password: ")
97+
pass, err := term.ReadPassword(int(syscall.Stdin))
98+
if err != nil {
99+
fmt.Print(andotp.FormatError(err.Error()))
100+
}
101+
fmt.Print("\n")
102+
return string(pass)
103+
}

0 commit comments

Comments
 (0)