Created
May 22, 2023 13:55
-
-
Save janvhs/55178d08d0c357bfe754fed606cf2814 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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