diff --git a/ymt_v3/valid.md b/ymt_v3/valid.md new file mode 100644 index 0000000..37e9a03 --- /dev/null +++ b/ymt_v3/valid.md @@ -0,0 +1,272 @@ +// File: ymt_v3/crypto.go +```go +package ymt_v3 + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "reflect" + "sort" + "strings" + "time" + + "github.com/tjfoc/gmsm/sm4" +) + +const ( + EncryptModeAES = "aes" + EncryptModeSM4 = "sm4" +) + +func GenerateTimestamp() string { + return time.Now().Format("2006-01-02 15:04:05") +} + +func BuildSignString(appID, timestamp, ciphertext string) string { + return appID + timestamp + ciphertext +} + +func RSASign(signStr string, privateKeyPEM string) (string, error) { + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return "", fmt.Errorf("failed to decode PEM private key") + } + + var privateKey *rsa.PrivateKey + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse private key: %v", err) + } + privateKey = key.(*rsa.PrivateKey) + } else { + var ok bool + privateKey, ok = key.(*rsa.PrivateKey) + if !ok { + return "", fmt.Errorf("not a RSA private key") + } + } + + hash := sha256.Sum256([]byte(signStr)) + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:]) + if err != nil { + return "", fmt.Errorf("sign failed: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature), nil +} + +func RSAVerify(signStr, signature string, publicKeyPEM string) error { + block, _ := pem.Decode([]byte(publicKeyPEM)) + if block == nil { + return fmt.Errorf("failed to decode PEM public key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse public key: %v", err) + } + + publicKey, ok := pub.(*rsa.PublicKey) + if !ok { + return fmt.Errorf("not a RSA public key") + } + + sigBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return fmt.Errorf("failed to decode signature: %v", err) + } + + hash := sha256.Sum256([]byte(signStr)) + return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], sigBytes) +} + +func AESECBEncrypt(plaintext []byte, key []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", fmt.Errorf("create AES cipher failed: %v", err) + } + + padded := pkcs7Padding(plaintext, aes.BlockSize) + ciphertext := make([]byte, len(padded)) + for i := 0; i < len(padded); i += aes.BlockSize { + block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize]) + } + + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) { + ciphertext, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return nil, fmt.Errorf("decode base64 failed: %v", err) + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("create AES cipher failed: %v", err) + } + + if len(ciphertext)%aes.BlockSize != 0 { + return nil, fmt.Errorf("ciphertext length is not multiple of block size") + } + + plaintext := make([]byte, len(ciphertext)) + for i := 0; i < len(ciphertext); i += aes.BlockSize { + block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize]) + } + + plaintext, err = pkcs7UnPadding(plaintext) + if err != nil { + return nil, fmt.Errorf("remove padding failed: %v", err) + } + + return plaintext, nil +} + +func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) { + if len(key) != 16 { + return "", fmt.Errorf("SM4 key length must be 16 bytes") + } + + block, err := sm4.NewCipher(key) + if err != nil { + return "", fmt.Errorf("create SM4 cipher failed: %v", err) + } + + padded := pkcs7Padding(plaintext, block.BlockSize()) + + iv := make([]byte, block.BlockSize()) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", fmt.Errorf("generate IV failed: %v", err) + } + + mode := cipher.NewCBCEncrypter(block, iv) + ciphertext := make([]byte, len(padded)) + mode.CryptBlocks(ciphertext, padded) + + result := append(iv, ciphertext...) + return base64.StdEncoding.EncodeToString(result), nil +} + +func SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) { + if len(key) != 16 { + return nil, fmt.Errorf("SM4 key length must be 16 bytes") + } + + data, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return nil, fmt.Errorf("decode base64 failed: %v", err) + } + + block, err := sm4.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("create SM4 cipher failed: %v", err) + } + + blockSize := block.BlockSize() + if len(data) < blockSize { + return nil, fmt.Errorf("data length too short") + } + iv := data[:blockSize] + ciphertext := data[blockSize:] + + mode := cipher.NewCBCDecrypter(block, iv) + plaintext := make([]byte, len(ciphertext)) + mode.CryptBlocks(plaintext, ciphertext) + + plaintext, err = pkcs7UnPadding(plaintext) + if err != nil { + return nil, fmt.Errorf("remove padding failed: %v", err) + } + + return plaintext, nil +} + +func pkcs7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +func pkcs7UnPadding(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, fmt.Errorf("data is empty") + } + unpadding := int(data[length-1]) + if unpadding > length || unpadding == 0 { + return nil, fmt.Errorf("invalid padding") + } + return data[:length-unpadding], nil +} + +func StructToSortedJSON(v interface{}) (string, error) { + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return "", fmt.Errorf("expected struct, got %s", val.Kind()) + } + + typ := val.Type() + result := make(map[string]interface{}) + + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := typ.Field(i) + + jsonTag := fieldType.Tag.Get("json") + if jsonTag == "" || jsonTag == "-" { + continue + } + name := jsonTag + if idx := strings.Index(jsonTag, ","); idx != -1 { + name = jsonTag[:idx] + } + + if field.IsZero() { + continue + } + + result[name] = field.Interface() + } + + keys := make([]string, 0, len(result)) + for k := range result { + keys = append(keys, k) + } + sort.Strings(keys) + + var buf bytes.Buffer + buf.WriteString("{") + for i, k := range keys { + if i > 0 { + buf.WriteString(",") + } + buf.WriteString(`"`) + buf.WriteString(k) + buf.WriteString(`":`) + vBytes, err := json.Marshal(result[k]) + if err != nil { + return "", err + } + buf.Write(vBytes) + } + buf.WriteString("}") + return buf.String(), nil +} +``` \ No newline at end of file