549 lines
12 KiB
Go

package crypto
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"fmt"
"math/big"
"strings"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
"github.com/google/uuid"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
func GetFunctionList() map[string]luajit.GoFunction {
return map[string]luajit.GoFunction{
"base64_encode": base64_encode,
"base64_decode": base64_decode,
"base64_url_encode": base64_url_encode,
"base64_url_decode": base64_url_decode,
"hex_encode": hex_encode,
"hex_decode": hex_decode,
"md5_hash": md5_hash,
"sha1_hash": sha1_hash,
"sha256_hash": sha256_hash,
"sha512_hash": sha512_hash,
"hmac_sha256": hmac_sha256,
"hmac_sha1": hmac_sha1,
"uuid_generate": uuid_generate,
"uuid_generate_v4": uuid_generate_v4,
"uuid_validate": uuid_validate,
"random_bytes": random_bytes,
"random_hex": random_hex,
"random_string": random_string,
"secure_compare": secure_compare,
"argon2_hash": argon2_hash,
"argon2_verify": argon2_verify,
"bcrypt_hash": bcrypt_hash,
"bcrypt_verify": bcrypt_verify,
"scrypt_hash": scrypt_hash,
"scrypt_verify": scrypt_verify,
"pbkdf2_hash": pbkdf2_hash,
"pbkdf2_verify": pbkdf2_verify,
"password_hash": password_hash,
"password_verify": password_verify,
}
}
func base64_encode(s *luajit.State) int {
str := s.ToString(1)
encoded := base64.StdEncoding.EncodeToString([]byte(str))
s.PushString(encoded)
return 1
}
func base64_decode(s *luajit.State) int {
str := s.ToString(1)
decoded, err := base64.StdEncoding.DecodeString(str)
if err != nil {
s.PushNil()
s.PushString("invalid base64 data")
return 2
}
s.PushString(string(decoded))
return 1
}
func base64_url_encode(s *luajit.State) int {
str := s.ToString(1)
encoded := base64.URLEncoding.EncodeToString([]byte(str))
s.PushString(encoded)
return 1
}
func base64_url_decode(s *luajit.State) int {
str := s.ToString(1)
decoded, err := base64.URLEncoding.DecodeString(str)
if err != nil {
s.PushNil()
s.PushString("invalid base64url data")
return 2
}
s.PushString(string(decoded))
return 1
}
func hex_encode(s *luajit.State) int {
str := s.ToString(1)
encoded := hex.EncodeToString([]byte(str))
s.PushString(encoded)
return 1
}
func hex_decode(s *luajit.State) int {
str := s.ToString(1)
decoded, err := hex.DecodeString(str)
if err != nil {
s.PushNil()
s.PushString("invalid hex data")
return 2
}
s.PushString(string(decoded))
return 1
}
func md5_hash(s *luajit.State) int {
str := s.ToString(1)
hash := md5.Sum([]byte(str))
s.PushString(hex.EncodeToString(hash[:]))
return 1
}
func sha1_hash(s *luajit.State) int {
str := s.ToString(1)
hash := sha1.Sum([]byte(str))
s.PushString(hex.EncodeToString(hash[:]))
return 1
}
func sha256_hash(s *luajit.State) int {
str := s.ToString(1)
hash := sha256.Sum256([]byte(str))
s.PushString(hex.EncodeToString(hash[:]))
return 1
}
func sha512_hash(s *luajit.State) int {
str := s.ToString(1)
hash := sha512.Sum512([]byte(str))
s.PushString(hex.EncodeToString(hash[:]))
return 1
}
func hmac_sha256(s *luajit.State) int {
message := s.ToString(1)
key := s.ToString(2)
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(message))
s.PushString(hex.EncodeToString(h.Sum(nil)))
return 1
}
func hmac_sha1(s *luajit.State) int {
message := s.ToString(1)
key := s.ToString(2)
h := hmac.New(sha1.New, []byte(key))
h.Write([]byte(message))
s.PushString(hex.EncodeToString(h.Sum(nil)))
return 1
}
func uuid_generate(s *luajit.State) int {
id := uuid.New()
s.PushString(id.String())
return 1
}
func uuid_generate_v4(s *luajit.State) int {
id := uuid.New()
s.PushString(id.String())
return 1
}
func uuid_validate(s *luajit.State) int {
str := s.ToString(1)
_, err := uuid.Parse(str)
s.PushBoolean(err == nil)
return 1
}
func random_bytes(s *luajit.State) int {
length := int(s.ToNumber(1))
if length < 0 || length > 65536 {
s.PushNil()
s.PushString("invalid length")
return 2
}
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
s.PushNil()
s.PushString("failed to generate random bytes")
return 2
}
s.PushString(string(bytes))
return 1
}
func random_hex(s *luajit.State) int {
length := int(s.ToNumber(1))
if length < 0 || length > 32768 {
s.PushNil()
s.PushString("invalid length")
return 2
}
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
s.PushNil()
s.PushString("failed to generate random bytes")
return 2
}
s.PushString(hex.EncodeToString(bytes))
return 1
}
func random_string(s *luajit.State) int {
length := int(s.ToNumber(1))
if length < 0 || length > 65536 {
s.PushNil()
s.PushString("invalid length")
return 2
}
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
if s.GetTop() >= 2 && !s.IsNil(2) {
charset = s.ToString(2)
}
if len(charset) == 0 {
s.PushNil()
s.PushString("empty charset")
return 2
}
result := make([]byte, length)
charsetLen := big.NewInt(int64(len(charset)))
for i := range result {
n, err := rand.Int(rand.Reader, charsetLen)
if err != nil {
s.PushNil()
s.PushString("failed to generate random number")
return 2
}
result[i] = charset[n.Int64()]
}
s.PushString(string(result))
return 1
}
func secure_compare(s *luajit.State) int {
a := s.ToString(1)
b := s.ToString(2)
s.PushBoolean(hmac.Equal([]byte(a), []byte(b)))
return 1
}
func argon2_hash(s *luajit.State) int {
password := s.ToString(1)
time := uint32(1)
memory := uint32(64 * 1024)
threads := uint8(4)
keyLen := uint32(32)
if s.GetTop() >= 2 && !s.IsNil(2) {
time = uint32(s.ToNumber(2))
}
if s.GetTop() >= 3 && !s.IsNil(3) {
memory = uint32(s.ToNumber(3))
}
if s.GetTop() >= 4 && !s.IsNil(4) {
threads = uint8(s.ToNumber(4))
}
if s.GetTop() >= 5 && !s.IsNil(5) {
keyLen = uint32(s.ToNumber(5))
}
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
s.PushNil()
s.PushString("failed to generate salt")
return 2
}
hash := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen)
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
result := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
memory, time, threads, encodedSalt, encodedHash)
s.PushString(result)
return 1
}
func argon2_verify(s *luajit.State) int {
password := s.ToString(1)
hash := s.ToString(2)
parts := strings.Split(hash, "$")
if len(parts) != 6 || parts[1] != "argon2id" {
s.PushBoolean(false)
return 1
}
var memory, time uint32
var threads uint8
if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads); err != nil {
s.PushBoolean(false)
return 1
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
s.PushBoolean(false)
return 1
}
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
s.PushBoolean(false)
return 1
}
actualHash := argon2.IDKey([]byte(password), salt, time, memory, threads, uint32(len(expectedHash)))
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
return 1
}
func bcrypt_hash(s *luajit.State) int {
password := s.ToString(1)
cost := 12
if s.GetTop() >= 2 && !s.IsNil(2) {
cost = int(s.ToNumber(2))
if cost < 4 || cost > 31 {
s.PushNil()
s.PushString("invalid cost (must be 4-31)")
return 2
}
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
if err != nil {
s.PushNil()
s.PushString("bcrypt hash failed")
return 2
}
s.PushString(string(hash))
return 1
}
func bcrypt_verify(s *luajit.State) int {
password := s.ToString(1)
hash := s.ToString(2)
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
s.PushBoolean(err == nil)
return 1
}
func scrypt_hash(s *luajit.State) int {
password := s.ToString(1)
N := 32768 // CPU cost
r := 8 // block size
p := 1 // parallelization
keyLen := 32 // key length
if s.GetTop() >= 2 && !s.IsNil(2) {
N = int(s.ToNumber(2))
}
if s.GetTop() >= 3 && !s.IsNil(3) {
r = int(s.ToNumber(3))
}
if s.GetTop() >= 4 && !s.IsNil(4) {
p = int(s.ToNumber(4))
}
if s.GetTop() >= 5 && !s.IsNil(5) {
keyLen = int(s.ToNumber(5))
}
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
s.PushNil()
s.PushString("failed to generate salt")
return 2
}
hash, err := scrypt.Key([]byte(password), salt, N, r, p, keyLen)
if err != nil {
s.PushNil()
s.PushString("scrypt hash failed")
return 2
}
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
result := fmt.Sprintf("$scrypt$N=%d,r=%d,p=%d$%s$%s", N, r, p, encodedSalt, encodedHash)
s.PushString(result)
return 1
}
func scrypt_verify(s *luajit.State) int {
password := s.ToString(1)
hash := s.ToString(2)
parts := strings.Split(hash, "$")
if len(parts) != 5 || parts[1] != "scrypt" {
s.PushBoolean(false)
return 1
}
var N, r, p int
if _, err := fmt.Sscanf(parts[2], "N=%d,r=%d,p=%d", &N, &r, &p); err != nil {
s.PushBoolean(false)
return 1
}
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
if err != nil {
s.PushBoolean(false)
return 1
}
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
s.PushBoolean(false)
return 1
}
actualHash, err := scrypt.Key([]byte(password), salt, N, r, p, len(expectedHash))
if err != nil {
s.PushBoolean(false)
return 1
}
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
return 1
}
func pbkdf2_hash(s *luajit.State) int {
password := s.ToString(1)
iterations := 100000
keyLen := 32
if s.GetTop() >= 2 && !s.IsNil(2) {
iterations = int(s.ToNumber(2))
}
if s.GetTop() >= 3 && !s.IsNil(3) {
keyLen = int(s.ToNumber(3))
}
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
s.PushNil()
s.PushString("failed to generate salt")
return 2
}
hash := pbkdf2.Key([]byte(password), salt, iterations, keyLen, sha256.New)
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
result := fmt.Sprintf("$pbkdf2-sha256$i=%d$%s$%s", iterations, encodedSalt, encodedHash)
s.PushString(result)
return 1
}
func pbkdf2_verify(s *luajit.State) int {
password := s.ToString(1)
hash := s.ToString(2)
parts := strings.Split(hash, "$")
if len(parts) != 5 || parts[1] != "pbkdf2-sha256" {
s.PushBoolean(false)
return 1
}
var iterations int
if _, err := fmt.Sscanf(parts[2], "i=%d", &iterations); err != nil {
s.PushBoolean(false)
return 1
}
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
if err != nil {
s.PushBoolean(false)
return 1
}
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
s.PushBoolean(false)
return 1
}
actualHash := pbkdf2.Key([]byte(password), salt, iterations, len(expectedHash), sha256.New)
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
return 1
}
func password_hash(s *luajit.State) int {
password := s.ToString(1)
algorithm := "argon2id" // default
if s.GetTop() >= 2 && !s.IsNil(2) {
algorithm = s.ToString(2)
}
switch algorithm {
case "argon2id":
s.PushString(password)
return argon2_hash(s)
case "bcrypt":
s.PushString(password)
if s.GetTop() >= 3 {
s.PushNumber(s.ToNumber(3))
}
return bcrypt_hash(s)
case "scrypt":
s.PushString(password)
return scrypt_hash(s)
case "pbkdf2":
s.PushString(password)
return pbkdf2_hash(s)
default:
s.PushNil()
s.PushString("unsupported algorithm: " + algorithm)
return 2
}
}
func password_verify(s *luajit.State) int {
hash := s.ToString(2)
// Auto-detect algorithm from hash format
if strings.HasPrefix(hash, "$argon2id$") {
return argon2_verify(s)
} else if strings.HasPrefix(hash, "$2a$") || strings.HasPrefix(hash, "$2b$") || strings.HasPrefix(hash, "$2y$") {
return bcrypt_verify(s)
} else if strings.HasPrefix(hash, "$scrypt$") {
return scrypt_verify(s)
} else if strings.HasPrefix(hash, "$pbkdf2-sha256$") {
return pbkdf2_verify(s)
}
s.PushBoolean(false)
return 1
}