业务上的原因需要做个类似功能开关的设计,要求服务端能控制客户端的一些功能开启和关闭,但要求整个流程是安全的(如果报文被截胡并修改,客户端的某些危险功能就不能关闭了)。思来想去,最后决定使用
Curve25519
对具体的功能内容进行加密,再使用配套
Ed25519
算法进行验签的形式进行
说明
- 这里我们假设一个常见的场景:
A
对B
发起询问,以获取某些需要进行加密的内容。我们对B
向A
发送的消息进行加密解密,以保证通信过程中不被第三者干扰。
前期准备
服务端
- 使用
go
语言搭建
客户端
- 使用
Flutter
实现,请确保pubspec.yaml
中拥有以下库:
name: untitled1
description:"A new Flutter project."# pub.dev using `flutter pub publish`. This is preferred for private packages.publish_to:'none'# Remove this line if you wish to publish to pub.devversion: 1.0.0+1
environment:sdk:'>=3.2.6 <4.0.0'dependencies:flutter:sdk: flutter
cupertino_icons: ^1.0.2
retrofit: ^4.1.0
json_annotation: ^4.8.1
flutter_lints: ^3.0.1
cryptography: ^2.7.0
cryptography_flutter: ^2.3.2
dio: ^5.4.0
logger: ^2.0.2+1
dev_dependencies:flutter_test:sdk: flutter
# For information on the generic Dart part of this file, see the# following page: https://dart.dev/tools/pub/pubspec# The following section is specific to Flutter packages.flutter:# The following line ensures that the Material Icons font is# included with your application, so that you can use the icons in# the material Icons class.uses-material-design:true# To add assets to your application, add an assets section, like this:# assets:# - images/a_dot_burr.jpeg# - images/a_dot_ham.jpeg# An image asset can refer to one or more resolution-specific "variants", see# https://flutter.dev/assets-and-images/#resolution-aware# For details regarding adding assets from package dependencies, see# https://flutter.dev/assets-and-images/#from-packages# To add custom fonts to your application, add a fonts section here,# in this "flutter" section. Each entry in this list should have a# "family" key with the font family name, and a "fonts" key with a# list giving the asset and other descriptors for the font. For# example:# fonts:# - family: Schyler# fonts:# - asset: fonts/Schyler-Regular.ttf# - asset: fonts/Schyler-Italic.ttf# style: italic# - family: Trajan Pro# fonts:# - asset: fonts/TrajanPro.ttf# - asset: fonts/TrajanPro_Bold.ttf# weight: 700## For details regarding fonts from package dependencies,# see https://flutter.dev/custom-fonts/#from-packages
客户端
绘制页面
- 我们只要展现一个简单的页面即可,一行是
A
发出去的消息,一行是从B
接受到的消息 main.dart
import'package:flutter/material.dart';import'package:untitled1/home_page.dart';voidmain(){runApp(MyApp());}classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnMaterialApp(
home:HomePage(),);}}
home_page.dart
import'dart:math';import'package:flutter/material.dart';classHomePageextendsStatefulWidget{String content ="Hello Bob!";String received ="";@overrideState<StatefulWidget>createState()=>HomePageState();}classHomePageStateextendsState<HomePage>{@overridevoidinitState(){super.initState();}@overridevoiddispose(){super.dispose();}@overrideWidgetbuild(BuildContext context){returnScaffold(
body:Column(
children:[Text('Send from A: ${widget.content}'),Text('Received from B: ${widget.received}'),],));}}
消息加密
生成Ed25519密钥对
- 如果你想了解
Ed25519
,可以通过这里:Ed25519,也可以在其它博客找到更通俗易懂的解释 - 我们需要生成一对密钥对,消息的发送者负责使用私钥对消息进行签名操作,而消息的接收者负责使用公钥对消息的内容进行验签,证明消息的来源没问题(不是别人伪冒发送者发送的错误消息)
- 生成操作在两端都可以进行
- 客户端:
Future<void>generateKey()async{var ed25519 =FlutterEd25519(Ed25519());var keyPair =await ed25519.newKeyPair();var privateKeyBytes =await keyPair.extractPrivateKeyBytes();var publicKey =await keyPair.extractPublicKey();var publicKeyBytes = publicKey.bytes;Logger().i("privateKey: ${base64Encode(privateKeyBytes)}");Logger().i("publicKey: ${base64Encode(publicKeyBytes)}");}
- 服务端:
funcgenerateKey(){
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)if err !=nil{panic(err)}
log.Println("privateKey:", base64.StdEncoding.EncodeToString(privateKey))
log.Println("publicKey:", base64.StdEncoding.EncodeToString(publicKey))}
- 选一个地方生成即可,不过需要注意:
go
中PrivateKey
的生成结果是一个长度为64
的列表,PublicKey
的内容为其后32
字节。而标准的Ed25519
的密钥对,私钥和公钥的长度都为32
,也就是说,privateKey
的前32
字节才是真实的私钥,但因为这个库中后续的各种操作都需要一个64
字节的列表进行,所以这里不提取出真实的私钥。而base64
编码的目的自然是为了方便储存和发送,我们需要生成一对密钥,用于客户端验证服务端发送的消息途中没有出现问题。这里展示我生成的密钥:- 私钥:EsJERpGFpUvqbLMiqTUAxtZfuywCRyjQah2WJmdpl/dQqtZ01I7kju8D39zNwHzsDAcwLxAxZxTIF4a8Pj17BQ==
- 公钥:UKrWdNSO5I7vA9/czcB87AwHMC8QMWcUyBeGvD49ewU=
- 服务端需要保存:自己的私钥
- 客户端需要保存:服务端的公钥
Curve25519密钥交换
Curve25519
需要在两端都生成一个密钥对。下面我简述一下整个消息的发送流程:- 客户端:我们假设消息刚开始由客户端发出,客户端需要在本地随机生成一个Curve25519
或X25519
密钥对,将生成的公钥携带在消息中进行发出- 服务端:获取到客户端生成的公钥,在本地随机生成一个Curve25519
或X25519
密钥对,将自己的私钥与客户端的公钥进行计算,获得一个共享密钥(SharedKey),接下来使用任一对称加密算法对明文进行加密,将生成的公钥和密文一同发送给客户端- 客户端:接收到服务端生成的公钥和密文,将服务端的公钥和自己之前生成的私钥进行计算,获得一个共享密钥(ShardKey),通过相同的对称加密算法对密文解密,得到明文- 注意:Curve25519
算法保证两边最后计算得到的共享密钥是完全相同的,如果最后计算得到的结果不同,可能是数据传输过程中被他人修改了,即使这样,他人也无法获取密文中的内容- 服务端:
package main
import("crypto/aes""crypto/cipher""crypto/ed25519""crypto/rand""encoding/base64""github.com/gin-gonic/gin""golang.org/x/crypto/curve25519""log")type Message struct{
Content string`json:"content,omitempty"`
PublicKey string`json:"public_key,omitempty"`
Sign string`json:"sign,omitempty"`}type InternalContent struct{
Data []string`json:"data,omitempty"`}const signPrivateKey ="dwCJcOXj/46vBlSlcXHFTLvnII1gVCY8pFYqNfTXwIOq2NkLWFYg2GwZ9aU8Pq8rxahoa1ZRjhdGm6yPD7aQ4Q=="funcserver(clientX25519PublicKey string) Message {/// clientPublicKey: 客户端发来的X25519公钥,用于计算shareKey/// privateKey: 本地生成的Ed25519私钥,用于最后对内容的签名var clientPublicKey, privateKey [32]byte/// 将本地的Ed25519私钥准备好,用于最后的签名
signPriKey,_:= base64.StdEncoding.DecodeString(signPrivateKey)/// ----------- 随机生成X25519密钥 -----------_, err := rand.Read(privateKey[:])if err !=nil{panic(err)return Message{}}
privateKey[0]&=248
privateKey[31]&=127
privateKey[31]|=64/// ----------- 随机生成X25519密钥 -----------/// 解码客户端发来的X25519公钥
publicKeyDec,_:= base64.URLEncoding.DecodeString(clientX25519PublicKey)copy(clientPublicKey[:], publicKeyDec)/// 将客户端发来的X25519公钥和服务端生成的私钥计算shareKey
sharedKey, err := curve25519.X25519(privateKey[:], clientPublicKey[:])if err !=nil{panic(err)}/// 通过本地随机生成的X25519计算相配套的公钥
publicKey,_:= curve25519.X25519(privateKey[:], curve25519.Basepoint)/// 将计算得到的shareKey和明文使用对称加密算法进行加密/// 这里用的是ACM-GCM 256bits
enc,_:=encrypt(sharedKey,"Hello Alice")return Message{
Content: base64.StdEncoding.EncodeToString(enc),
PublicKey: base64.StdEncoding.EncodeToString(publicKey[:]),/// 注意这里,把加密后的内容进行签名
Sign: base64.StdEncoding.EncodeToString(ed25519.Sign(signPriKey, enc)),}}funcmain(){
router := gin.Default()
router.GET("/message",func(context *gin.Context){
key := context.Query("key")
response :=server(key)
context.JSON(200, response)})
router.Run(":9000")}// / 使用shareKey将明文加密funcencrypt(key []byte, plaintext string)([]byte,error){
block, err := aes.NewCipher(key)if err !=nil{panic(err)returnnil, err
}
gcm, err := cipher.NewGCM(block)if err !=nil{panic(err)returnnil, err
}
nonce :=make([]byte, gcm.NonceSize())if_, err := rand.Read(nonce); err !=nil{panic(err)returnnil, err
}
ciphertext := gcm.Seal(nonce, nonce,[]byte(plaintext),nil)return ciphertext,nil}funcgenerateKey(){
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)if err !=nil{panic(err)}
log.Println("privateKey:", base64.StdEncoding.EncodeToString(privateKey))
log.Println("publicKey:", base64.StdEncoding.EncodeToString(publicKey))}
- 客户端:
import'dart:convert';import'dart:ffi';import'dart:math';import'package:cryptography/cryptography.dart';import'package:cryptography_flutter/cryptography_flutter.dart';import'package:dio/dio.dart';import'package:flutter/material.dart';import'package:logger/logger.dart';classHomePageextendsStatefulWidget{String content ="Hello Bob!";String received ="";@overrideState<StatefulWidget>createState()=>HomePageState();}classHomePageStateextendsState<HomePage>{@overridevoidinitState(){super.initState();}@overrideWidgetbuild(BuildContext context){returnScaffold(
body:Column(
mainAxisAlignment:MainAxisAlignment.center,
crossAxisAlignment:CrossAxisAlignment.start,
children:[Text('Send from A: ${widget.content}',
style:TextStyle(
fontSize:30,),),FutureBuilder(
future:signAndDecryptMessage(),
builder:(content,snapshot){if(!snapshot.hasData)returnSizedBox.shrink();returnText('Received from B: ${snapshot.data}',
style:TextStyle(
fontSize:30,),);}),],));}Future<String>signAndDecryptMessage()async{var signPublicKey ="UKrWdNSO5I7vA9/czcB87AwHMC8QMWcUyBeGvD49ewU=";var x25519 =FlutterX25519(X25519());var ed25519 =FlutterEd25519(Ed25519());var keyPair =await x25519.newKeyPair();var publicKey =await keyPair.extractPublicKey();var dio =Dio()..interceptors.add(LogInterceptor(
requestBody:true,
responseBody:true,));var response =await dio.get(
"http://10.129.24.45:9000/message?key=${base64UrlEncode(
publicKey.bytes)}");var json = response.data asMap<String,dynamic>;var sign =await ed25519.verify(base64Decode(json["content"]),
signature:Signature(base64Decode(json["sign"]),
publicKey:SimplePublicKey(base64Decode(signPublicKey),
type:KeyPairType.ed25519
),));if(!sign)Logger().e("Sign Failed");var shareKey =await x25519.sharedSecretKey(
keyPair: keyPair,
remotePublicKey:SimplePublicKey(base64Decode(json["public_key"]),
type:KeyPairType.x25519
));var secretKey =SecretKey(await shareKey.extractBytes());var algorithm =AesGcm.with256bits();var plainText =await algorithm.decryptString(SecretBox.fromConcatenation(base64Decode(json["content"]),
nonceLength:AesGcm.defaultNonceLength,
macLength:AesGcm.aesGcmMac.macLength,),
secretKey: secretKey
);Logger().i("plainText: ${plainText}");return plainText;}}
- 注意:这里在用
url
的参数追加key
时,使用了baseUrlEncode
,以保证最后的base64
是url safe
的(不会出现会导致错误解析的&
等字符)
本文转载自: https://blog.csdn.net/qq_45861632/article/details/141534741
版权归原作者 EricMoin 所有, 如有侵权,请联系我们删除。
版权归原作者 EricMoin 所有, 如有侵权,请联系我们删除。