Skip to content

Instantly share code, notes, and snippets.

@janvhs
Created May 22, 2023 13:55
Show Gist options
  • Save janvhs/55178d08d0c357bfe754fed606cf2814 to your computer and use it in GitHub Desktop.
Save janvhs/55178d08d0c357bfe754fed606cf2814 to your computer and use it in GitHub Desktop.
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
"filippo.io/age"
"golang.org/x/crypto/argon2"
)
const saltLen = 16
func main() {
err := mainE()
if err != nil {
panic(err)
}
}
func mainE() error {
k, err := age.GenerateX25519Identity()
if err != nil {
return err
}
recp := k.Recipient().String()
fmt.Printf("original: %s\n", recp)
password := []byte("0100")
data := []byte(recp)
ciphertext, err := Encrypt(password, data)
if err != nil {
return err
}
fmt.Printf("ciphertext: %s\n", hex.EncodeToString(ciphertext))
startGuess := time.Now()
guessed, err := guessPassword(ciphertext)
if err != nil {
return err
}
finishGuess := time.Now()
plaintext, err := Decrypt(guessed, ciphertext)
if err != nil {
return err
}
fmt.Printf("plaintext: %s\n", plaintext)
fmt.Printf("equals: %t\n", string(plaintext) == recp)
timeNeeded := finishGuess.Sub(startGuess).Seconds()
fmt.Printf("Needed %f seconds\n", timeNeeded)
pwAsInt, _ := strconv.Atoi(string(password))
timePerAttempt := timeNeeded / float64(pwAsInt+1)
fmt.Printf("Needed %f seconds per attempt\n", timePerAttempt)
return nil
}
func guessPassword(ciphertext []byte) ([]byte, error) {
var password []byte
for attempt := 0; attempt <= 9999; attempt++ {
attemptAsString := fmt.Sprint(attempt)
strlen := len(attemptAsString)
if strlen < 4 {
missingZeros := 4 - strlen
zeros := strings.Repeat("0", missingZeros)
attemptAsString = zeros + attemptAsString
}
attemptAsBytes := []byte(attemptAsString)
_, err := Decrypt(attemptAsBytes, ciphertext)
if err == nil {
password = attemptAsBytes
break
}
}
if len(password) == 0 {
return password, fmt.Errorf("no password found")
}
return password, nil
}
func Encrypt(key, data []byte) ([]byte, error) {
key, salt, err := DeriveKey(key, nil)
if err != nil {
return nil, err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
ciphertext = append(ciphertext, salt...)
return ciphertext, nil
}
func Decrypt(key, data []byte) ([]byte, error) {
salt, data := data[len(data)-saltLen:], data[:len(data)-saltLen]
key, _, err := DeriveKey(key, salt)
if err != nil {
return nil, err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
func DeriveKey(password, salt []byte) ([]byte, []byte, error) {
if salt == nil {
salt = make([]byte, saltLen)
if _, err := rand.Read(salt); err != nil {
return nil, nil, err
}
}
// You could also use 2*1024*1024
key := argon2.IDKey(password, salt, 1, 1024*1024, 4, 32)
return key, salt, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment