跳到主要内容

Demo - Go

使用 golang 编程语言编写的请求示例。


rsa_util

package rsa

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"errors"
)

func PrivateKey(keyData string) (*rsa.PrivateKey, error) {

//block, rest := pem.Decode([]byte(keyData))
//if block == nil {
// return nil, errors.New("failed to decode PEM block containing the private key")
//}
//
//rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(rest)
//if err != nil {
// return nil, err
//}

decoded, err := base64.StdEncoding.DecodeString(keyData)
if err != nil {
return nil, err
}

privateKey, err := x509.ParsePKCS8PrivateKey(decoded)
if err != nil {
return nil, err
}

rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("unable to extract RSA private key")
}

return rsaPrivateKey, nil
}

func PublicKey(keyData string) (*rsa.PublicKey, error) {

//block, rest := pem.Decode([]byte(keyData))
//if block == nil {
// return nil, errors.New("failed to decode PEM block containing the public key")
//}
//
//rsaPublicKey, err := x509.ParsePKCS1PublicKey(rest)
//if err != nil {
// return nil, err
//}

decoded, err := base64.StdEncoding.DecodeString(keyData)
if err != nil {
return nil, err
}

publicKey, err := x509.ParsePKIXPublicKey(decoded)
if err != nil {
return nil, err
}

rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.New("unable to extract RSA public key")
}

return rsaPublicKey, nil
}

func Encrypt(publicKey *rsa.PublicKey, plains string) (string, error) {

//encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte(plains))
//if err != nil {
// return "", err
//}

// cipher := base64.StdEncoding.EncodeToString(encrypted)

encrypted := []byte(plains)
blockSize := (publicKey.N.BitLen() / 8) - 11

// Split the data into blocks
var blocks [][]byte
for blockSize < len(encrypted) {
encrypted, blocks = encrypted[blockSize:], append(blocks, encrypted[0:blockSize:blockSize])
}
blocks = append(blocks, encrypted)

// Encrypt each block
var ciphers []byte
for _, block := range blocks {
cipher, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, block)
if err != nil {
return "", err
}
ciphers = append(ciphers, cipher...)
}

cipher := base64.StdEncoding.EncodeToString(ciphers)
return cipher, nil
}

func Decrypt(privateKey *rsa.PrivateKey, cipher string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(cipher)
if err != nil {
return "", err
}

//plains, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decoded)
//if err != nil {
// return "", err
//}

blockSize := privateKey.N.BitLen() / 8

// Split the data into blocks
var blocks [][]byte
for blockSize < len(decoded) {
decoded, blocks = decoded[blockSize:], append(blocks, decoded[0:blockSize:blockSize])
}
blocks = append(blocks, decoded)

// Decrypt each block
var plains []byte
for _, block := range blocks {
plain, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, block)
if err != nil {
return "", err
}
plains = append(plains, plain...)
}

return string(plains), nil
}

func Sign(privateKey *rsa.PrivateKey, data string) (string, error) {
h := sha256.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
if err != nil {
return "", err
}
encoded := base64.StdEncoding.EncodeToString(signature)
return encoded, nil
}

func Verify(publicKey *rsa.PublicKey, data string, sign string) (bool, error) {
h := sha256.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
decoded, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return false, err
}
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed, decoded)
if err != nil {
return false, err
}
return true, nil
}


fire_demo

package fire

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"golang/rsa"
"io"
"net/http"
"strings"
"testing"
"time"
)

// 服务商(agency)的编号
// 此处使用 agency_no = 100000000000000000 作为模拟
// 真实请求场景,请咨询 ice-run 的技术支持获取正式的 服务商编号 agency_no
const AgencyNo = "100000000000000000"

// 安全算法 RSA
const Security = "RSA"

// ice-run 的 公钥
const PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC78QqLKTkVcwvB2Scm4IoImOXmIthVmx4HHr5Im09aol2r2UlMK3jl4lamdxktvjFswWx41hQBD4tyYFHTY+mQJsRK8Zwm7LZmLJDHcgibIuLxEddln+MI+rWwT+SOdOt7lBvd9PcO4nYr/3Nqkyk8L4PrT+GF7dpVk8iE/VpTwQIDAQAB"

// agency 的 私钥
const PrivateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOyC76Y+/2NqkkIVUtAHkbZkBYI2XXkuqE9o4oIDkbyAY04jC5MLa3oVc3uGG7T/Elodo9dIbIxNh7/ir7GQLR/hY+ookHAewurJ1z4Jn5TQeRk3+nYQcX6a6orfUOMffwcn0Jtat8D+cNAu3uCi+N7Ez/yNpItNORYqTa8AkgzrAgMBAAECgYAS/rsfl3ysb+E6RHsnsQvvYZ4dpJ8iPfCPnCVg+sdoI8mV+3ORBkBGCFYDjDRKd5fyO+IuRqdNJ2bpLtwcfy9YchcV/x40GIuAlre6A6qt4Raq+0l4FJufaqgdmSiLxd5zPX79gdMvBqTbdYzCSDGP5Pf3pTqIS3iKvSixFTQBzQJBAO6k6rE6JE/EHrbRjVtZNitDZ8rouCL0s9nGsgIDBSfgOCClO+HlZKcHq6W9ry8j1gOFeGH2rF1yMslA/ZymRL0CQQD9tk/gk9Xs1Eg1if7SLwvNxUP+z96oEqQTF26tSudPRwOO88fnfE4ZZ4heBt1QI+s7IhG39qecsFW269nvKfbHAkEArXmEgUBalQFjslGyB+1Zyyk8keuJrx9ifbRKQdwgK1R6eICkfxlZiXGx/NFeP041jGnBkLTXpzYUZOexc+YJoQJBANR3r87vnxAU+l+zr62O7oCk+XtT0y/HZJYEYpBHEQyn+MfnSXqG89R8iovLjd0GJ4E+173KlrU2SqHEQ57w8pMCQAEoDjFUXpYzBl8e0M9XsSpGY531mbOl8ASnVp4OSyavaCsn56eEkL9TI5q69KrYUEA4PN3BazMWfll2s/wu6G0="

// ice-run 的 test 环境的 API 网关 的域名地址
// 正式生产环境的域名地址请查阅 ice-run 的技术文档
const Domain = "https://test-fire-api.ice.run"

// 示例地址,仅用于测试加密解密加签验签流程,无任何实际业务场景
const Demo = Domain + "/demo/api/demo"

// 请求 body 结构
type Request struct {
Param string `json:"param"`
}

// 响应 body 结构
type Response struct {
Code string `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
}

// 模拟请求 demo 接口
func TestFireHttp(t *testing.T) {

url := Demo
fmt.Println("request url :", url)

// 对方公钥
publicKey, err := rsa.PublicKey(PublicKey)
if err != nil {
t.Fatal(err)
}

// 己方私钥
privateKey, err := rsa.PrivateKey(PrivateKey)
if err != nil {
t.Fatal(err)
}

// 参数对象
// 此处使用 map 模拟,实际场景可以定义数据模型结构体
// 注意:不可传 null 或 空字符串 或 单值 ( string, number, boolean )
// 如果当前业务接口不需要传递具体参数,可以传空对象参数 {},json 序列化为 `{"param":{}}`
paramObject := map[string]interface{}{"name": "喵喵汪汪"}
paramBytes, err := json.Marshal(paramObject)
if err != nil {
t.Fatal(err)
}

// 参数明文:参数对象的 json 序列化后的字符串的明文
paramPlains := string(paramBytes)
fmt.Println("param plains :", paramPlains)

// 参数密文:agency 使用 ice-run 公钥 对参数明文加密后得到的密文
paramCipher, err := rsa.Encrypt(publicKey, paramPlains)
if err != nil {
t.Fatal(err)
}
fmt.Println("param cipher :", paramCipher)

// 请求签名:agency 使用 私钥 对参数密文进行签名
// 当 ice-run 收到请求时,将会使用 agency 公钥 进行验签
sign, err := rsa.Sign(privateKey, paramCipher)
if err != nil {
t.Fatal(err)
}
fmt.Println("request sign : ", sign)

// 请求对象
// json 结构,此处使用自定义结构体模拟
// body 数据的 键 固定为 param ,值为 json 序列化后的字符串 RSA 加密后的密文
requestObject := Request{Param: paramCipher}
requestBytes, err := json.Marshal(requestObject)
if err != nil {
t.Fatal(err)
}
// 请求体原文
requestBody := string(requestBytes)
fmt.Println("request body :", requestBody)

// 请求时间,当前世界标准时间的字符串,格式为 RFC-1123 标准,服务器会校验时间差,不允许超过设置的时间间隔(默认为 2 分钟)
xTime := strings.Replace(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT", -1)

// 链路追踪号(防止重放攻击的请求编号):单位时间内(默认为 1 分钟)不重复的字符串,建议使用 uuid 或者时间戳+随机数。限制长度 = 32。正则表达式 = ^[0-9a-f]{32}$
randomBytes := make([]byte, 16)
_, err = io.ReadFull(rand.Reader, make([]byte, 16))
if err != nil {
t.Fatal(err)
}
xTrace := hex.EncodeToString(randomBytes)

// http 客户端
// 此处使用 golang 标准库模拟,实际场景建议自行选择 HTTP 客户端组件包
// 发起 HTTP 请求之前,请确认 ${DOMAIN} 可以正常访问,部分客户端的服务器环境可能需要设置请求代理
client := &http.Client{}
request, err := http.NewRequest("POST", Demo, strings.NewReader(requestBody))
if err != nil {
t.Fatal(err)
}

// 请求头
request.Header.Add("Content-Type", "application/json")
request.Header.Add("X-Client", AgencyNo)
request.Header.Add("X-Security", Security)
request.Header.Add("X-Sign", sign)
request.Header.Add("X-Time", xTime)
request.Header.Add("X-Trace", xTrace)

fmt.Println("request header : ", request.Header)

// 发送请求
response, err := client.Do(request)
if err != nil {
t.Fatal(err)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
t.Fatal(err)
}
}(response.Body)

// 响应状态码
statusCode := response.StatusCode
fmt.Println("response status :", statusCode)

// 响应 header
fmt.Println("response header :", response.Header)

sign = response.Header.Get("X-Sign")
fmt.Println("response sign :", sign)

// 响应 body ,json 格式的字符串
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
responseBody := string(bodyBytes)
fmt.Println("response body :", responseBody)

// 响应对象
// json 结构,此处使用自定义结构体模拟
// 数据的 键 有 code 和 message 和 data
// 其中 data 为 json 序列化后的字符串 RSA 加密后的密文
var responseObject Response
err = json.Unmarshal(bodyBytes, &responseObject)
if err != nil {
t.Fatal(err)
}

// 响应码,默认 000000 表示请求成功
code := responseObject.Code
fmt.Println("response code :", code)

// 响应 数据密文
dataCipher := responseObject.Data
fmt.Println("data cipher :", dataCipher)

// 响应验签:ice-run 处理响应时,会使用 ice-run 私钥对响应密文进行签名
// agency 收到响应后,需要使用 ice-run 公钥进行验签
verify, err := rsa.Verify(publicKey, dataCipher, sign)
if err != nil || !verify {
t.Fatal(errors.New("verification failed"))
}

// 响应 数据明文
dataPlains, err := rsa.Decrypt(privateKey, dataCipher)
if err != nil {
t.Fatal(err)
}
fmt.Println("data plains :", dataPlains)

var data map[string]any
err = json.Unmarshal([]byte(dataPlains), &data)
if err != nil {
t.Fatal(err)
}

}