commit b56ebf3c3d90709a53830d09b3486c95183de1d6 Author: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed Sep 6 10:32:32 2023 +0530 Scaffold project diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..33f94e490 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +data/** +.DS_Store +Photos.code-workspace +logs/** +.idea/** +.vscode/** +tmp/** +museum.yaml diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..3c0c2065c --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module cli-go + +go 1.20 + +require ( + github.com/jamesruan/sodium v1.0.14 + golang.org/x/crypto v0.11.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f3366a91a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go new file mode 100644 index 000000000..1bd56ae14 --- /dev/null +++ b/internal/crypto/crypto.go @@ -0,0 +1,70 @@ +package crypto + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "log" + + "github.com/jamesruan/sodium" + "golang.org/x/crypto/argon2" +) + +// deriveArgonKey generates a 32-bit cryptographic key using the Argon2id algorithm. +// Parameters: +// - password: The plaintext password to be hashed. +// - salt: The salt as a base64 encoded string. +// - memLimit: The memory limit in bytes. +// - opsLimit: The number of iterations. +// +// Returns: +// - A byte slice representing the derived key. +// - An error object, which is nil if no error occurs. +func deriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, error) { + if memLimit < 1024 || opsLimit < 1 { + return nil, fmt.Errorf("invalid memory or operation limits") + } + + // Decode salt from base64 + saltBytes, err := base64.StdEncoding.DecodeString(salt) + if err != nil { + return nil, fmt.Errorf("invalid salt: %v", err) + } + + // Generate key using Argon2id + // Note: We're assuming a fixed key length of 32 bytes and changing the threads + key := argon2.IDKey([]byte(password), saltBytes, uint32(opsLimit), uint32(memLimit/1024), 1, 32) + + return key, nil +} + +// decryptChaCha20poly1305 decrypts the given data using the ChaCha20-Poly1305 algorithm. +// Parameters: +// - data: The encrypted data as a byte slice. +// - key: The key for decryption as a byte slice. +// - nonce: The nonce for decryption as a byte slice. +// +// Returns: +// - A byte slice representing the decrypted data. +// - An error object, which is nil if no error occurs. +func decryptChaCha20poly1305(data []byte, key []byte, nonce []byte) ([]byte, error) { + reader := bytes.NewReader(data) + header := sodium.SecretStreamXCPHeader{Bytes: nonce} + decoder, err := sodium.MakeSecretStreamXCPDecoder( + sodium.SecretStreamXCPKey{Bytes: key}, + reader, + header) + if err != nil { + log.Println("Failed to make secret stream decoder", err) + return nil, err + } + // Buffer to store the decrypted data + decryptedData := make([]byte, len(data)) + n, err := decoder.Read(decryptedData) + if err != nil && err != io.EOF { + log.Println("Failed to read from decoder", err) + return nil, err + } + return decryptedData[:n], nil +} diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go new file mode 100644 index 000000000..ac59b6bbe --- /dev/null +++ b/internal/crypto/crypto_test.go @@ -0,0 +1,53 @@ +package crypto + +import ( + "encoding/base64" + "testing" +) + +const ( + password = "test_password" + kdfSalt = "vd0dcYMGNLKn/gpT6uTFTw==" + memLimit = 64 * 1024 * 1024 // 64MB + opsLimit = 2 + cipherText = "kBXQ2PuX6y/aje5r22H0AehRPh6sQ0ULoeAO" + cipherNonce = "v7wsI+BFZsRMIjDm3rTxPhmi/CaUdkdJ" + expectedPlainText = "plain_text" + expectedDerivedKey = "vp8d8Nee0BbIML4ab8Cp34uYnyrN77cRwTl920flyT0=" +) + +func TestDeriveArgonKey(t *testing.T) { + derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit) + if err != nil { + t.Fatalf("Failed to derive key: %v", err) + } + + if base64.StdEncoding.EncodeToString(derivedKey) != expectedDerivedKey { + t.Fatalf("Derived key does not match expected key") + } +} + +func TestDecryptChaCha20poly1305(t *testing.T) { + derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit) + if err != nil { + t.Fatalf("Failed to derive key: %v", err) + } + + decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText) + if err != nil { + t.Fatalf("Failed to decode cipher text: %v", err) + } + + decodedCipherNonce, err := base64.StdEncoding.DecodeString(cipherNonce) + if err != nil { + t.Fatalf("Failed to decode cipher nonce: %v", err) + } + + decryptedText, err := decryptChaCha20poly1305(decodedCipherText, derivedKey, decodedCipherNonce) + if err != nil { + t.Fatalf("Failed to decrypt: %v", err) + } + if string(decryptedText) != expectedPlainText { + t.Fatalf("Decrypted text : %s does not match the expected text: %s", string(decryptedText), expectedPlainText) + } +}