DEV Community

shaas Matam
shaas Matam

Posted on

Bridging the Gap: Cross-Language Encryption and Decryption between Node.js and Go

In the fast-evolving world of software development, ensuring the security of data as it moves between different systems and platforms is paramount. Recently, I found myself facing a unique challenge: implementing a secure, cross-language encryption and decryption mechanism using Node.js and Go. Asymmetric encryption, with its promise of robust security through public and private key pairs, seemed like the perfect solution. However, achieving compatibility between these two languages proved to be a formidable task.

Image description

This guide aims to help you seamlessly integrate security features across different programming environments with a clear, step-by-step approach to mastering cross-language asymmetric encryption.

Asymmetric encryption, also known as public-key cryptography, is a type of encryption that uses a pair of keys for encryption and decryption: a public key and a private key.

Here’s how it works:

Key Components:
Public Key: This key is shared openly and can be distributed widely. It is used to encrypt data.
Private Key: This key is kept secret and is known only to the owner. It is used to decrypt data.

Node.js Encryption with OAEP and SHA-256

const fs = require('fs');
const crypto = require('crypto');

// Load the public key
const publicKey = fs.readFileSync('path/to/public_key.pem', 'utf8');

// Encrypt with OAEP padding
const Encrypt = (textToEncrypt) => {
    return crypto.publicEncrypt(
      {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: "sha256",
      },
      Buffer.from(textToEncrypt)
    ).toString('utf8');
};

// Test encryption
const encryptedText = Encrypt("Hello, World!");
console.log("Encrypted Text:", encryptedText);
Enter fullscreen mode Exit fullscreen mode

Node.js Decryption with OAEP and SHA-256

const fs = require('fs');
const crypto = require('crypto');

// Load the private key
const privateKey = fs.readFileSync('path/to/private_key.pem', 'utf8');

// Decrypt with OAEP padding
const decryptWithPrivateKeyOaep = (encryptedText) => {
    const buffer = Buffer.from(encryptedText, 'utf8');
    const decrypted = crypto.privateDecrypt(
      {
          key: privateKey,
          padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
          oaepHash: "sha256",
      },
      buffer
    );
    return decrypted.toString();
};

// Test decryption
const decryptedText = decryptWithPrivateKeyOaep(encryptedText);
console.log("Decrypted Text:", decryptedText);
Enter fullscreen mode Exit fullscreen mode

Encryption (Golang)

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "io/ioutil"
    "log"

    "crypto/x509"
    "encoding/pem"
)

// Function to load public key
func loadPublicKey(path string) (*rsa.PublicKey, error) {
    pubBytes, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, err
    }

    block, _ := pem.Decode(pubBytes)
    if block == nil || block.Type != "PUBLIC KEY" {
        return nil, fmt.Errorf("failed to decode PEM block containing public key")
    }

    return x509.ParsePKCS1PrivateKey(block.Bytes)
}

// Function to encrypt with public key using OAEP padding
func encryptWithPublicKey(publicKey *rsa.PublicKey, textToEncrypt string) (string, error) {
    encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, []byte(textToEncrypt), nil)
    if err != nil {
        fmt.Println("error while encrypting", err)
        return "", err
    }
    return base64.StdEncoding.EncodeToString(encryptedBytes), nil
}

func main() {
    // Load public key
    publicKey, err := loadPublicKey("path/to/public_key.pem")
    if err != nil {
        log.Fatalf("failed to load public key: %v", err)
    }

    // Test encryption
    encryptedText, err := encryptWithPublicKey(publicKey, "Hello, World!")
    if err != nil {
        log.Fatalf("failed to encrypt: %v", err)
    }

    fmt.Println("Encrypted Text:", encryptedText)
}

Enter fullscreen mode Exit fullscreen mode

Decryption (Golang)

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "io/ioutil"
    "log"

    "crypto/x509"
    "encoding/pem"
)

// Function to load private key
func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
    privBytes, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, err
    }

    block, _ := pem.Decode(privBytes)
    if block == nil || block.Type != "RSA PRIVATE KEY" {
        return nil, fmt.Errorf("failed to decode PEM block containing private key")
    }

    return x509.ParsePKCS1PrivateKey(block.Bytes)
}

// Function to decrypt with private key using OAEP padding
func decryptWithPrivateKeyOAEP(privateKey *rsa.PrivateKey, encryptedText string) (string, error) {
    encryptedBytes, err := base64.StdEncoding.DecodeString(encryptedText)
    if err != nil {
        return "", fmt.Errorf("error decoding base64 string: %v", err)
    }

    decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedBytes, nil)
    if err != nil {
        return "", fmt.Errorf("error while decrypting: %v", err)
    }

    return string(decryptedBytes), nil
}

func main() {
    // Load private key
    privateKey, err := loadPrivateKey("path/to/private_key.pem")
    if err != nil {
        log.Fatalf("failed to load private key: %v", err)
    }

    // Example encrypted text (from the Node.js encryption)
    encryptedText := "YOUR_ENCRYPTED_TEXT_HERE"

    // Test decryption
    decryptedText, err := decryptWithPrivateKeyOAEP(privateKey, encryptedText)
    if err != nil {
        log.Fatalf("failed to decrypt: %v", err)
    }

    fmt.Println("Decrypted Text:", decryptedText)
}

Enter fullscreen mode Exit fullscreen mode

Understanding RSA Padding Schemes

Node.js Padding Schemes

1. RSA_PKCS1_PADDING:

Description: This is the traditional RSA padding scheme, also known as PKCS#1 v1.5. It is less secure compared to OAEP and is not recommended for new implementations.

Default Behavior: If no padding scheme is specified, Node.js's crypto.publicEncrypt defaults to using RSA_PKCS1_PADDING.

2. RSA_PKCS1_OAEP_PADDING:

Description: This is the newer and more secure padding scheme called Optimal Asymmetric Encryption Padding (OAEP). It includes additional randomness and hash functions to increase security.

Explicit Specification: When you specify padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, you are explicitly telling the function to use this padding scheme.

Go Equivalent Padding Schemes

In Go, the standard library crypto/rsa provides support for both PKCS#1 v1.5 and OAEP padding schemes.

Image description

Concluding Remarks:

I hope this guide helps you navigate the complexities of cross-language encryption and decryption between Node.js and Go.

By understanding and correctly implementing the corresponding padding schemes, you can ensure secure data transmission across different programming environments.

Happy coding!

Top comments (1)

Collapse
 
plutov profile image
Alex Pliutau

Great write-up, we have a bunch of articles on Go in our Newsletter, check it out - packagemain.tech