I'm trying to decrypt the data in golang using the in-inbuild crypto library, the data is encrypted in flutter/dart using steel_crypt library. The specific message that is thrown by the golang's crypto library is: panic: cipher: message authentication failed.

I'm running flutter app on Android amulator (localhost is: 10.0.2.2)

Golang version: go1.17.6 linux/amd64 | Flutter version: Flutter 2.8.1 • channel stable | Dart version: Dart 2.15.1

Golang Code

package main

import (
    "bufio"
    "crypto"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/json"
    "encoding/pem"
    "errors"
    "fmt"
    "io"
    "net"
)

func Encode(data []byte) string {
    hb := base64.StdEncoding.EncodeToString([]byte(data))
    return hb
}

// Decoding the base string to array of bytes
func Decode(data string) []byte {
    hb, _ := base64.StdEncoding.DecodeString(data)
    return hb
}

// Generating RSA private key
func GenerateRsaPrivateKey(size int) (*rsa.PrivateKey, error) {
    privateKey, err := rsa.GenerateKey(rand.Reader, size)
    if err != nil {
        return nil, err
    }
    return privateKey, nil
}

// Generating RSA public key
func GenerateRsaPublicKey(privateKey *rsa.PrivateKey) rsa.PublicKey {
    return privateKey.PublicKey
}

// This function can be use encrypt a plain text with rsa algorithm
func RsaEncrypt(publicKey rsa.PublicKey, data string) ([]byte, error) {

    encryptedBytes, err := rsa.EncryptPKCS1v15(
        rand.Reader,
        &publicKey,
        []byte(data))
    return encryptedBytes, err

    // encryptedBytes, err := rsa.EncryptOAEP(
    //  sha256.New(),
    //  rand.Reader,
    //  &publicKey,
    //  []byte(data),
    //  nil)
    // return encryptedBytes, err
}

// This function can be use decrypt a encrypted text with rsa algorithm
func RsaDecrypt(privateKey rsa.PrivateKey, data []byte) ([]byte, error) {
    decryptedBytes, err := privateKey.Decrypt(
        nil,
        data,
        &rsa.OAEPOptions{Hash: crypto.SHA256})
    return decryptedBytes, err
}

//  This fucntion is used to dump/serialize the rsa public key
func DumpKey(key *rsa.PublicKey) ([]byte, error) {
    return x509.MarshalPKCS1PublicKey(key), nil
}

// This function is used to load the rsa public key
func LoadKey(byteKey []byte) (*rsa.PublicKey, error) {
    key, err := x509.ParsePKCS1PublicKey(byteKey)
    return key, err
}

// Generate fixed size byte array
func GenerateAesKey(size int) []byte {
    token := make([]byte, size)
    rand.Read(token)
    return token
}

// This fucntion can be used for encrypting a plain text using AES-GCM algorithm
func AesEncryption(key []byte, data string) ([]byte, error) {
    c, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(c)
    if err != nil {
        return nil, err
    }

    nonce := make([]byte, gcm.NonceSize())
    if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }

    cipherText := gcm.Seal(nonce, nonce, []byte(data), nil)
    return cipherText, nil
}

// This fucntion can be used for decrypting the ciphertext encrypted using AES-GCM algorithm
func AesDecryption(key []byte, cipherText []byte, nonce []byte) ([]byte, error) {
    c, err := aes.NewCipher(key)
    if err != nil {
        fmt.Println("1")
        return nil, err
    }
    gcm, err := cipher.NewGCM(c)

    if err != nil {
        fmt.Println("2")
        return nil, err
    }

    noncesize := gcm.NonceSize()
    if len(cipherText) < noncesize {
        fmt.Println("3")
        return nil, err
    }

    cipherText = cipherText[noncesize:]

    // nonce, cipherText := cipherText[:noncesize], cipherText[noncesize:]

    plainText, err := gcm.Open(nil, nonce, cipherText, nil)

    if err != nil {
        fmt.Println("4", err.Error())
        return nil, err
    }

    return plainText, nil
}

func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
    block, _ := pem.Decode([]byte(pubPEM))
    if block == nil {
        return nil, errors.New("failed to parse PEM block containing the key")
    }

    pub, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return nil, err
    }

    switch pub := pub.(type) {
    case *rsa.PublicKey:
        return pub, nil
    default:
        break // fall through
    }
    return nil, errors.New("Key type is not RSA")
}

type trans struct {
    Key string `json:"key"`
}

type creden struct {
    Data  []byte `json:"data"`
    Nonce []byte `json:"nonce"`
}

func startServer() {
    fmt.Println("Starting Server...")
    l, err := net.Listen("tcp", "127.0.0.1:8080")

    if err != nil {
        panic(err)
    }

    defer l.Close()

    c, err := l.Accept()

    if err != nil {
        panic(err)
    }

    fmt.Println("Client Connected: ", c)

    data, err := bufio.NewReader(c).ReadBytes('\n')

    if err != nil {
        panic(err)
    }

    var t trans

    err_ := json.Unmarshal(data, &t)

    if err != nil {
        panic(err_)
    }

    // fmt.Println("Key: ", t.Key)

    publicKey, e := ParseRsaPublicKeyFromPemStr(t.Key)

    if e != nil {
        panic(e)
    }

    // fmt.Println("Success", publicKey)

    var cre creden
    cre.Data = GenerateAesKey(32)
    cre.Nonce = GenerateAesKey(12)

    jsonRes, err := json.Marshal(cre)

    if err != nil {
        panic(err)
    }

    cipherText, e_r := RsaEncrypt(*publicKey, string(jsonRes))

    if e_r != nil {
        panic(e_r)
    }

    fmt.Println("cipherText: ", len(cipherText), " | ", cipherText)

    encodedCipherText := Encode(cipherText)

    n, err := c.Write([]byte(encodedCipherText))

    if err != nil {
        panic(err)
    }

    fmt.Println("Written back the response. Written Bytes: ", n)

    data2, err2 := bufio.NewReader(c).ReadBytes('\n')

    if err2 != nil {
        panic(err2)
    }

    var t2 trans

    err_2 := json.Unmarshal(data2, &t2)

    if err_2 != nil {
        panic(err_2)
    }

    fmt.Println("recv data: ", t2.Key)

    cipherText2 := Decode(t2.Key)

    fmt.Println("cipherText2: ", cipherText2)

    plainText2, err := AesDecryption(cre.Data, cipherText2, cre.Nonce)

    if err != nil {
        panic(err)
    }

    fmt.Println("plainText: ", plainText2)

}

func main() {
    startServer()
}

Flutter Code

External Libraries:

  1. steel_crypt flutter pub add steel_crypt.

  2. crypton flutter pub add crypton.

Just add below function in your flutter project and call it just before returning the MaterialApp(). If you call this function below this level it will get executed every time windows refres.

void connectFunc() async {
  Socket socket = await Socket.connect('10.0.2.2', 8080);
  print(socket);
  print("Connected...");

  // listen to the received data event stream

  RSAKeypair rsaKeypair = RSAKeypair.fromRandom();

  socket.listen((List<int> event) {
    String base64Response = utf8.decode(event);
    // dynamic response = base64.decode(base64Response);

    // print(response.length);
    // print(response.runtimeType);

    try {
      String plainText = rsaKeypair.privateKey.decrypt(base64Response);
      print(plainText);

      dynamic jsonData = jsonDecode(plainText);

      String Key = jsonData["data"];
      String Nonce = jsonData["nonce"];

      var aes = AesCrypt(key: Key, padding: PaddingAES.pkcs7);

      print('AES Symmetric GCM:');
      var crypted = aes.gcm.encrypt(inp: 'words', iv: Nonce); //encrypt
      // print(crypted);
      // print(aes.gcm.decrypt(enc: crypted, iv: Nonce)); //decrypt
      // print('');

      print("send data: " + crypted);

      dynamic dictData = {
        "key": crypted,
      };

      socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
    } catch (e) {
      print(e);
    }

    // dynamic response = jsonDecode(jsonResponse);
    // String key = response["data"];
    // String nonce = response["nonce"];
  });
  // RSAKeypair rsaKeypair = RSAKeypair.fromRandom();

  dynamic dictData = {
    "key": rsaKeypair.publicKey.toFormattedPEM(),
  };

  socket.add(utf8.encode(jsonEncode(dictData) + "\n"));

  // send hello
  // socket.add(utf8.encode('hello from flutter/dart'));

  // return socket;
}


Solution 1: shikhar.code

I have found the solution of the problem. The solution is that we have to use another library called cryptography to encrypt the data in flutter/dart. There is no error at golang side ( except some modifications ).

AesDecrypt function at the golang side have been modified

func AesDecryption(key []byte, cipherText []byte, nonce []byte) ([]byte, error) {
    c, err := aes.NewCipher(key)
    if err != nil {
        fmt.Println("1")
        return nil, err
    }
    gcm, err := cipher.NewGCM(c)

    if err != nil {
        fmt.Println("2")
        return nil, err
    }

    // noncesize := gcm.NonceSize()
    // if len(cipherText) < noncesize {
    //  fmt.Println("3")
    //  return nil, err
    // }

    // cipherText = cipherText[noncesize:]

    // nonce, cipherText := cipherText[:noncesize], cipherText[noncesize:]

    plainText, err := gcm.Open(nil, nonce, cipherText, nil)

    if err != nil {
        fmt.Println("4", err.Error())
        return nil, err
    }

    return plainText, nil
}

Now the whole revised startServer function in here:

func startServer(nonce []byte, key []byte) {
    fmt.Println("Starting Server...")
    l, err := net.Listen("tcp", "127.0.0.1:8080")

    if err != nil {
        panic(err)
    }

    defer l.Close()

    c, err := l.Accept()

    if err != nil {
        panic(err)
    }

    fmt.Println("Client Connected: ", c)

    data, err := bufio.NewReader(c).ReadBytes('\n')

    if err != nil {
        panic(err)
    }

    var t trans

    err_ := json.Unmarshal(data, &t)

    if err != nil {
        panic(err_)
    }

    // fmt.Println("Key: ", t.Key)

    publicKey, e := ParseRsaPublicKeyFromPemStr(t.Key)

    if e != nil {
        panic(e)
    }

    // fmt.Println("Success", publicKey)

    var cre creden
    cre.Data = Encode(key)    //GenerateAesKey(32)
    cre.Nonce = Encode(nonce) //GenerateAesKey(12)

    jsonRes, err := json.Marshal(cre)

    if err != nil {
        panic(err)
    }

    cipherText, e_r := RsaEncrypt(*publicKey, string(jsonRes))

    if e_r != nil {
        panic(e_r)
    }

    // fmt.Println("cipherText: ", len(cipherText), " | ", cipherText)

    encodedCipherText := Encode(cipherText)

    n, err := c.Write([]byte(encodedCipherText))

    if err != nil {
        panic(err)
    }

    fmt.Println("Written back the response. Written Bytes: ", n)

    data2, err2 := bufio.NewReader(c).ReadBytes('\n')

    if err2 != nil {
        panic(err2)
    }

    var t2 trans

    err_2 := json.Unmarshal(data2, &t2)

    if err_2 != nil {
        panic(err_2)
    }

    fmt.Println("recv data: ", t2.Key)

    cipherText2 := Decode(t2.Key)

    fmt.Println("cipherText2: ", cipherText2)

    plainText2, err := AesDecryption(key, cipherText2, nonce)

    if err != nil {
        panic(err)
    }

    fmt.Println("plainText: ", string(plainText2))

}

And now the flutter side of solution: external denepencies: cryptography: ^2.0.5

void connectFunc() async {
  Socket socket = await Socket.connect('10.0.2.2', 8080);
  print(socket);
  print("Connected...");

  // listen to the received data event stream

  RSAKeypair rsaKeypair = RSAKeypair.fromRandom();

  socket.listen((List<int> event) async {
    String base64Response = utf8.decode(event);

    try {
      String plainText = rsaKeypair.privateKey.decrypt(base64Response);

      dynamic jsonData = jsonDecode(plainText);

      String Key = jsonData["data"];
      String Nonce = jsonData["nonce"];

      String message = "Hello World";

      final algorithm = AesGcm.with256bits();

      final secretBox = await algorithm.encrypt(
        message.codeUnits,
        secretKey: SecretKey(base64.decode(jsonData["data"])),
        nonce: base64.decode(jsonData["nonce"]),
      );

      print("key: ${base64.decode(jsonData["data"])}");
      print("nonce: ${base64.decode(jsonData["nonce"])}");
      print("secretBox: ${secretBox.concatenation(nonce: false)}");

      dynamic dictData = {
        "key": base64.encode(secretBox.concatenation(nonce: false)),
      };

      socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
    } catch (e) {
      print(e);
    }
  });

  dynamic dictData = {
    "key": rsaKeypair.publicKey.toFormattedPEM(),
  };

  socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
}