0


绝对安全?使用Curve25519和Ed25519为发送的消息加密

业务上的原因需要做个类似功能开关的设计,要求服务端能控制客户端的一些功能开启和关闭,但要求整个流程是安全的(如果报文被截胡并修改,客户端的某些危险功能就不能关闭了)。思来想去,最后决定使用

Curve25519

对具体的功能内容进行加密,再使用配套

Ed25519

算法进行验签的形式进行

说明

  • 这里我们假设一个常见的场景:AB发起询问,以获取某些需要进行加密的内容。我们对BA发送的消息进行加密解密,以保证通信过程中不被第三者干扰。

前期准备

服务端

  • 使用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))}
  • 选一个地方生成即可,不过需要注意:goPrivateKey的生成结果是一个长度为64的列表,PublicKey的内容为其后32字节。而标准的Ed25519的密钥对,私钥和公钥的长度都为32,也就是说,privateKey的前32字节才是真实的私钥,但因为这个库中后续的各种操作都需要一个64字节的列表进行,所以这里不提取出真实的私钥。而base64编码的目的自然是为了方便储存和发送,我们需要生成一对密钥,用于客户端验证服务端发送的消息途中没有出现问题。这里展示我生成的密钥:- 私钥:EsJERpGFpUvqbLMiqTUAxtZfuywCRyjQah2WJmdpl/dQqtZ01I7kju8D39zNwHzsDAcwLxAxZxTIF4a8Pj17BQ==- 公钥:UKrWdNSO5I7vA9/czcB87AwHMC8QMWcUyBeGvD49ewU=
  • 服务端需要保存:自己的私钥
  • 客户端需要保存:服务端的公钥

Curve25519密钥交换

  • Curve25519需要在两端都生成一个密钥对。下面我简述一下整个消息的发送流程:- 客户端:我们假设消息刚开始由客户端发出,客户端需要在本地随机生成一个Curve25519X25519密钥对,将生成的公钥携带在消息中进行发出- 服务端:获取到客户端生成的公钥,在本地随机生成一个Curve25519X25519密钥对,将自己的私钥客户端的公钥进行计算,获得一个共享密钥(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,以保证最后的base64url safe的(不会出现会导致错误解析的&等字符)
标签: 安全 go flutter

本文转载自: https://blog.csdn.net/qq_45861632/article/details/141534741
版权归原作者 EricMoin 所有, 如有侵权,请联系我们删除。

“绝对安全?使用Curve25519和Ed25519为发送的消息加密”的评论:

还没有评论