Обзор
Сначала синхронизируйте обзор проекта:
Была опубликована последняя статья, Routing Middleware - Jaeger Link Tracking (Actual Combat), реакция на статью была действительно неожиданной, также был перенаправлен публичный аккаунт "Go China", и многие друзья добавили меня для общения, назвав меня Богом, в факт Какой я бог, я просто практикую это локально. Я все еще новичок в использовании языка Go. Спасибо за вашу любовь здесь!
В этой статье мы делимся: промежуточное ПО для маршрутизации — проверка подписи.
Зачем использовать проверку подписи?
Излишне говорить, что это в основном для обеспечения безопасности интерфейса и идентификации личности вызывающего абонента.Основываясь на этих двух моментах, давайте вместе разработаем подпись.
Вызывающий абонент должен подать заявку на получение ключа приложения и секрета приложения. Ключ приложения используется для идентификации личности вызывающего абонента, а секрет приложения используется для шифрования и создания подписи.
Конечно, сгенерированная подпись также должна соответствовать следующим пунктам:
- Изменяемость: подпись каждый раз должна быть разной.
- Своевременность: своевременность каждого запроса, недействительного по истечении срока действия.
- Уникальность: Каждая подпись уникальна.
- Целостность: входящие данные могут быть проверены для предотвращения вмешательства.
Например:
/api?param_1=xxx¶m_2=xxx
, где param_1 и param_2 — два параметра.
Если добавлена проверка подписи, необходимо передать еще несколько параметров:
- ak представляет собой ключ приложения, который используется для идентификации вызывающего абонента.
- ts представляет отметку времени, которая используется для проверки своевременности интерфейса.
- sn представляет зашифрованную строку подписи, которая используется для проверки целостности данных и предотвращения подделки данных.
sn зашифрован с помощью секрета приложения и переданных параметров.
Окончательные передаваемые параметры следующие:
/api?param_1=xxx¶m_2=xxx&ak=xxx&ts=xxx&sn=xxx
Вот отладочный трюк, слишком хлопотно вручную генерировать параметры ts и sn каждый раз при передачеdebug=1
Когда ts и sn будут возвращены, это будет ясно, если вы посмотрите на код.
В этой статье представлены три способа реализации подписей, а именно: комбинированное шифрование MD5, симметричное шифрование AES и асимметричное шифрование RSA.
Без лишних слов, давайте перейдем к теме.
MD5 комбинация
Создать подпись
Сначала оберните метод Go MD5:
func MD5(str string) string {
s := md5.New()
s.Write([]byte(str))
return hex.EncodeToString(s.Sum(nil))
}
Чтобы зашифровать:
appKey = "demo"
appSecret = "xxx"
encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx"
// 自定义验证规则
sn = MD5(appSecret + encryptStr + appSecret)
Подтвердить подпись
При передаче параметров подпись генерируется повторно, если переданная подпись сравнивается с сгенерированной подписью.
То же самое, это означает, что проверка подписи прошла успешно.
отличается, что указывает на то, что проверка подписи не удалась.
Промежуточное ПО — реализация кода
var AppSecret string
// MD5 组合加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := util.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["md5"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := util.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": createSign(req),
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" || sn != createSign(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) string {
// 自定义 MD5 组合
return util.MD5(AppSecret + createEncryptStr(params) + AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
Симметричное шифрование AES
Прежде чем использовать его, давайте сначала разберемся, что такое симметричное шифрование?
Симметричное шифрование заключается в использовании одного и того же ключа для шифрования и дешифрования.Этот метод называется симметричным шифрованием.
Обычно используемые алгоритмы: DES, AES.
Среди них AES — это обновленная версия DES с большей длиной ключа, большим выбором, большей гибкостью, более высокой безопасностью и более высокой скоростью.Давайте начнем шифрование AES напрямую.
преимущество
Алгоритм открытый, объем вычислений небольшой, скорость шифрования высокая, эффективность шифрования высокая.
недостаток
Отправитель и получатель должны согласовать ключ, и тогда обе стороны могут сохранить ключ, и управление ключами становится бременем обеих сторон.
Сценарии применения
Объем данных или шифрование критических данных относительно велик.
Создать подпись
Во-первых, инкапсулируйте метод шифрования Go AesEncrypt и метод дешифрования AesDecrypt.
// 加密 aes_128_cbc
func AesEncrypt (encryptStr string, key []byte, iv string) (string, error) {
encryptBytes := []byte(encryptStr)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
encryptBytes = pkcs5Padding(encryptBytes, blockSize)
blockMode := cipher.NewCBCEncrypter(block, []byte(iv))
encrypted := make([]byte, len(encryptBytes))
blockMode.CryptBlocks(encrypted, encryptBytes)
return base64.URLEncoding.EncodeToString(encrypted), nil
}
// 解密
func AesDecrypt (decryptStr string, key []byte, iv string) (string, error) {
decryptBytes, err := base64.URLEncoding.DecodeString(decryptStr)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
decrypted := make([]byte, len(decryptBytes))
blockMode.CryptBlocks(decrypted, decryptBytes)
decrypted = pkcs5UnPadding(decrypted)
return string(decrypted), nil
}
func pkcs5Padding (cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}
func pkcs5UnPadding (decrypted []byte) []byte {
length := len(decrypted)
unPadding := int(decrypted[length-1])
return decrypted[:(length - unPadding)]
}
Чтобы зашифровать:
appKey = "demo"
appSecret = "xxx"
encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx"
sn = AesEncrypt(encryptStr, appSecret)
Подтвердить подпись
decryptStr = AesDecrypt(sn, app_secret)
Сравните зашифрованную строку с расшифрованной строкой.
Если то же самое, проверка подписи прошла успешно.
отличается, что указывает на то, что проверка подписи не удалась.
Промежуточное ПО — реализация кода
var AppSecret string
// AES 对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := util.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["aes"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := util.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := util.AesDecrypt(sn, []byte(AppSecret), AppSecret)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return util.AesEncrypt(createEncryptStr(params), []byte(AppSecret), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
Асимметричное шифрование RSA
Как и выше, прежде чем использовать его, давайте сначала разберемся, что такое асимметричное шифрование?
Для асимметричного шифрования требуется два ключа для шифрования и дешифрования. Два ключа — открытый ключ и закрытый ключ.Этот метод называется асимметричным шифрованием.
Обычно используемый алгоритм: RSA.
преимущество
По сравнению с симметричным шифрованием безопасность выше.Шифрование и дешифрование требуют разных ключей, и как открытый, так и закрытый ключи могут быть зашифрованы и расшифрованы друг с другом.
недостаток
Шифрование и дешифрование отнимают много времени и медленны, и подходят только для шифрования небольшого объема данных.
Сценарии применения
Он подходит для сценариев с высокими требованиями к безопасности, подходит для шифрования небольшого объема данных, таких как платежные данные, данные для входа и т. д.
Создать подпись
Во-первых, инкапсулируйте метод шифрования с открытым ключом GO RSAPUBLICENCRYPT и метод дешифрования RSAPRIVATECRYPT.
// 公钥加密
func RsaPublicEncrypt(encryptStr string, path string) (string, error) {
// 打开文件
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
// 读取文件内容
info, _ := file.Stat()
buf := make([]byte,info.Size())
file.Read(buf)
// pem 解码
block, _ := pem.Decode(buf)
// x509 解码
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", err
}
// 类型断言
publicKey := publicKeyInterface.(*rsa.PublicKey)
//对明文进行加密
encryptedStr, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte(encryptStr))
if err != nil {
return "", err
}
//返回密文
return base64.URLEncoding.EncodeToString(encryptedStr), nil
}
// 私钥解密
func RsaPrivateDecrypt(decryptStr string, path string) (string, error) {
// 打开文件
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
// 获取文件内容
info, _ := file.Stat()
buf := make([]byte,info.Size())
file.Read(buf)
// pem 解码
block, _ := pem.Decode(buf)
// X509 解码
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
decryptBytes, err := base64.URLEncoding.DecodeString(decryptStr)
//对密文进行解密
decrypted, _ := rsa.DecryptPKCS1v15(rand.Reader,privateKey,decryptBytes)
//返回明文
return string(decrypted), nil
}
Вызывающий запрашивает открытый ключ и шифрует его:
appKey = "demo"
appSecret = "公钥"
encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx"
sn = RsaPublicEncrypt(encryptStr, appSecret)
Подтвердить подпись
decryptStr = RsaPrivateDecrypt(sn, app_secret)
Сравните зашифрованную строку с расшифрованной строкой.
Если то же самое, проверка подписи прошла успешно.
отличается, что указывает на то, что проверка подписи не удалась.
Промежуточное ПО — реализация кода
var AppSecret string
// RSA 非对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := util.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["rsa"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := util.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp - tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := util.RsaPrivateDecrypt(sn, config.AppRsaPrivateFile)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return util.RsaPublicEncrypt(createEncryptStr(params), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}
Как звонить?
Как и другие методы вызова промежуточного программного обеспечения, вы можете свободно выбирать в соответствии со своими потребностями.
Например, используя композицию MD5:
.Use(sign_md5.SetUp())
Используйте симметричное шифрование AES:
.Use(sign_aes.SetUp())
Используйте асимметричное шифрование RSA:
.Use(sign_rsa.SetUp())
Тестирование производительности
Поскольку RSA асимметричное шифрование является наиболее безопасным, оно должно использоваться равномерно.
НЕТ! НЕТ! НЕТ! Никогда!
Почему я должен волноваться, ведь я уже сталкивался с этой ямой, это урок слез ...
Давайте проверим производительность один за другим:
MD5
func Md5Test(c *gin.Context) {
startTime := time.Now()
appSecret := "IgkibX71IEf382PT"
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
count := 1000000
for i := 0; i < count; i++ {
// 生成签名
util.MD5(appSecret + encryptStr + appSecret)
// 验证签名
util.MD5(appSecret + encryptStr + appSecret)
}
utilGin := util.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
Имитация одного миллиона запросов, а время выполнения составляет около 1,1 ~ 1,2 с.
AES
func AesTest(c *gin.Context) {
startTime := time.Now()
appSecret := "IgkibX71IEf382PT"
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
count := 1000000
for i := 0; i < count; i++ {
// 生成签名
sn, _ := util.AesEncrypt(encryptStr, []byte(appSecret), appSecret)
// 验证签名
util.AesDecrypt(sn, []byte(appSecret), appSecret)
}
utilGin := util.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
Имитация одного миллиона запросов, а время выполнения составляет около 1,8 ~ 1,9 с.
RSA
func RsaTest(c *gin.Context) {
startTime := time.Now()
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
count := 500
for i := 0; i < count; i++ {
// 生成签名
sn, _ := util.RsaPublicEncrypt(encryptStr, "rsa/public.pem")
// 验证签名
util.RsaPrivateDecrypt(sn, "rsa/private.pem")
}
utilGin := util.Gin{Ctx: c}
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
}
Я не рискну моделировать миллион запросов, и не знаю, когда это будет сделано, давайте смоделируем 500 раз.
Имитировать 500 запросов, а время выполнения составляет около 1 с.
Вышеуказанное является моим локальным реализацией результатов, вы можете подвергнуть сомнению мою плохую производительность компьютеров, методы упаковки имеют проблему ...
Вы также можете попробовать и посмотреть, настолько ли велик разрыв в производительности.
Как взаимодействуют методы шифрования PHP и Go?
Я пишу PHP, можно ли реализовать метод генерации подписей в PHP?
Конечно может!
Я также реализовал вышеперечисленные 3 метода с помощью PHP, могут быть небольшие корректировки, но в целом проблема невелика, соответствующая демонстрация была загружена на github:
Хорошо, это все.
Адрес источника
серия статей go-gin-api
- 1. Инициализируйте проект с помощью модулей go
- 2. Планирование каталога проектов и проверка параметров
- 3. Routes Middleware — ведение журнала
- 4. Промежуточное ПО для маршрутизации — перехват исключений
- 5. Программное обеспечение промежуточного слоя для маршрутизации — трассировка ссылок Jaeger (теория)
- 6. Промежуточное ПО для маршрутизации — отслеживание ссылок Jaeger (Actual Combat)