It is a while ago, that I published the trsa module on NPM. Trsa my tiny wrapper around node-rsa to make it super simple to generate keys, encrypt-decrypt and sign-verify. Using this library it was also possible to build a module that enable to build secure webapps even without https (I recommend using https for sure, but it can take a few days to register so enable the encryption), and enabling the clients even to generate key pairs themselves on the fly can also be useful for authentication and other purposes.

But, this setup was for communication between Javascript and Javascript, browser and node.js, and I was never really sure, if it can work together with other technologies. I already found it working in javascript to use keys, that where generated using the openssl command line utilities. This time, I was looking to have Javascript communicate with Golang. Now let’s compare if the code will be clean in golang and last how the performance compare.

Plan

To do the test, I first generate a keypair using node.js and encrypt some data, write that to disc. Second, use golang to read the pem files and decrypt the message. After that worked, generate a keypair using golang encrypted a little message and also put that on disc. Last I verify using node.js if it can read the material generated by the go program and read the message encrypted go.

1. Start in Javascript

In javascript it was straight forward for me, just generate a keypair, write key files, encrypt the message and also write the encrypted message to file.

1
2
3
4
5
6
7
8
9
10
11
12
13
const fs = require('fs');
const rsa = require('trsa');

const keyPair = rsa.generateKeyPair()
console.log(keyPair);

fs.writeFileSync('./privateKey', keyPair.privateKey);
fs.writeFileSync('./publicKey', keyPair.publicKey);

const message = 'Hallo encrypted World ! ! !';
const encryptedMessage = rsa.encrypt(message, keyPair.publicKey);

fs.writeFileSync('./encryptedMessage', Buffer.from(encryptedMessage, 'hex'));

2. Read in Golang

To work in golang development still need some more time, I had to do some investigation. I was about to use a library to parse the private key file. But when looking at its code I decided to use the golang standard libraries. Still, some more tutorials online gave me a good direction. For some functions I needed to read more into the source code of node-rsa, to figure out what settings where used there. Surprising to me was, that the decryption function needed a hashing function. I found that node-rsa uses sha1, and yes, it worked out.

So, developing this part of was kind of tricky, but it worked out nicely in the end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"time"
)

func main() {

encryptedMessage, err := ioutil.ReadFile("encryptedMessage") // just pass the file name
if err != nil {
fmt.Print(err)
}

privateKeyPemBytes, err := ioutil.ReadFile("privatekey") // just pass the file name
if err != nil {
fmt.Print(err)
}

plaintext, err := decryptRSA(encryptedMessage, privateKeyPemBytes)
if err != nil {
fmt.Print(err)
}

fmt.Println(string(plaintext))
}

func decryptRSA(ct []byte, privateKeyPemBytes []byte) ([]byte, error) {
block, _ := pem.Decode(privateKeyPemBytes)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, errors.New("not a private key")
}

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

plaintext, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, privKey, ct, nil)
if err != nil {
return nil, err
}

return plaintext, nil
}

3. Encrypt some message using golang

After decrypting the message from JS using golang, I was pimped. Thinking I kind of only need to do the reverse the process. It turned out not as simple. For the key generation, creating a key was simple, and creating the pem.Block structure was also simple. But to encode the pen into bytes seemed unnecessary complex. 1. create Buffer, 2. create writer, 3. encode pem into the writer, 4. make a byte slice in the size of buffered length, 5. flush the data from writer to the buffer, 6. read the buffer into the byte slice. This needed to be done twice, once for private and once for the public key. Encrypting a message was more straight forward again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main

import (
"bufio"
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"time"
)

func main() {
privateKey, publicKey, err := generateKeys()
if err != nil {
fmt.Println(err)
return
}
ioutil.WriteFile("goPrivateKey", privateKey, 0)
ioutil.WriteFile("goPublicKey", publicKey, 0)

golangMessage := "Hallo Javascript from Golang"
encryptedMessage, err := encryptRSA([]byte(golangMessage), publicKey)
if err != nil {
fmt.Println(err)
return
}
ioutil.WriteFile("goEncryptedMessage", encryptedMessage, 0)
}
func encryptRSA(message []byte, publicKey []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil || block.Type != "PUBLIC KEY" {
return nil, errors.New("not a private key")
}

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

pubKey, isKey := pubKeyInterface.(*rsa.PublicKey)
if isKey != true {
return nil, err
}

encryptedMessage, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, pubKey, message, nil)
if err != nil {
return nil, err
}

return encryptedMessage, nil
}

func generateKeys() ([]byte, []byte, error) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

privBlock := pem.Block{}
privBlock.Type = "RSA PRIVATE KEY"
privBlock.Bytes = x509.MarshalPKCS1PrivateKey(privKey)

var privateKeyBuffer bytes.Buffer
privateKeyBufferWriter := bufio.NewWriter(&privateKeyBuffer)
err = pem.Encode(privateKeyBufferWriter, &privBlock)
if err != nil {
return nil, nil, err
}
privateKey := make([]byte, privateKeyBufferWriter.Buffered())
privateKeyBufferWriter.Flush()
privateKeyBuffer.Read(privateKey)

pubKey := privKey.Public()
publicKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, nil, err
}

pubBlock := pem.Block{}
pubBlock.Type = "PUBLIC KEY"
pubBlock.Bytes = publicKeyBytes
var publicKeyBuffer bytes.Buffer
publicKeyBufferWriter := bufio.NewWriter(&publicKeyBuffer)
err = pem.Encode(publicKeyBufferWriter, &pubBlock)
if err != nil {
return nil, nil, err
}
publicKey := make([]byte, publicKeyBufferWriter.Buffered())
publicKeyBufferWriter.Flush()
publicKeyBuffer.Read(publicKey)

return privateKey, publicKey, nil
}

4. decrypt using javascript

Decrypting the message in Javascript was straight forward, reading in the keys, decrypting and printing the message, was done in less then a minute, And I just hoped it to work. and it did, so lucky!!! the round trip is complete.

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs');
const rsa = require('trsa');

const keyPair = {
privateKey: fs.readFileSync('./goPrivateKey').toString(),
publicKey: fs.readFileSync('./goPublicKey').toString()
};

const goEncryptedMessage = fs.readFileSync('./goEncryptedMessage');
const messageFromGo = rsa.decrypt(goEncryptedMessage,keyPair.privateKey);

console.log(messageFromGo)

Code Comparison

To be honest, I am a little surprised, that in the go code it was possible to almost write the same as on the JS side, when looking at the main function. The imports where maintained automatically. And the functions I had to write, would go into a library, then the code in both languages looks surprisingly similar. It is still ugly that the errors continuously have to be checked using if err != nil { return err }. In Javascript thrown errors are using a second exit of a function. That as long as you only want to log the error can be catch in a single place. Having the static typing of golang is very useful, and even typescript does not get there. It is giving a very satisfying feeling, when the parts of the program snap into place and the code compiles.

Performance

Now, let’s do some little benchmark on the code. To do so, We just look at simple time measurements. In js we use Date.now() in golang we add take a little helper function found on the internet:

1
2
3
func makeTimestamp() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}

First we look at decrypt. It consists of parsing the key from a slice of bytes and the actual decryption. Adding a little loop to each of the two parts and repeating it 1000 times show that parsing the certificate takes 40ms vs the decryption of ~1550ms, so parsing the key, is like 3%. In Javascript I get to a similar result: parsing takes 80ms and 1150ms. The proportion is 7%. In a real world scenario this proportion will be much greater, as the messages we decrypted are very very small. So that gives me the opinion to use the keys in application code only as byte array, just as plain data. In golang we can still define a type like type PublicKey []byte;, to keep using the static typing.

But what struck me more, was that nodejs was actually able to decrypt 25% faster then in Golang. I think it might due to the implementation of rand.Reader. To generate random numbers is more implementation dependent than language dependent. As in a pure processing task like the parsing of the key, go was faster. Node.js can on this task compete with the implementation of golang, because the actual decryption is running in a native module implemented in C language.

For encrypting the direction turned. In go lang the program was able to create 10 key pairs with 2048bits in 1700ms. In nodejs the key generation took 28 to 40 seconde, that very inconsistent rates.
Decrypting the message 10 000 times, took in go 750ms and in Javascript 1450ms. So the encryption of our very short message was about 50% faster in go.

A Turning Page

Next I run into a new problem, when trying to encrypt bigger data. But golang did not encrypt data with more then a length of 214 bytes. Studying the node-rsa module some more, I saw some loops, that chunk the data and concatenate the results. Checking out some rsa libraries for golang, I found some that handle the large data in a similar way. for example x-rsa. The problem with it was it used a different hashing function that is not compatible with trsa.js. So the code was not to long, and I decided to make my own lib, that is not only compatible with the artifacts from js, but also had the same API.

benchmark, the printed numbers are in milliseconds (ms)

On a final test, I found that I can encrypt and decrypt about 25% quicker using node.js. Can you reproduce these findings? Do you think there is any flaw? Can you believe it? You can get trsa on github. Do you want to check what I have done? DOWNLOAD my project as zip.

Conclusion

For applications that heavily rely on encryption, it is good to consider nodejs. To my surprise however, the code that can be written in golang is very clean. The main functions look almost the same to do the task. And it will be easier to use all CPUs using go routines vs the separate processes using cluster module in node. I hope this article was fair and not to biased.

Contents
  1. 1. Plan
  2. 2. 1. Start in Javascript
  3. 3. 2. Read in Golang
  4. 4. 3. Encrypt some message using golang
  5. 5. 4. decrypt using javascript
  6. 6. Code Comparison
  7. 7. Performance
  8. 8. A Turning Page
  9. 9. Conclusion