neoatlantis-crypto-js is the encryption library implemented under JavaScript written by me with a complete set of features. This article will lead you to the usage of such features.
Because of the rich content inside the library, and the active development and debugging, this article will be continously updated. The title indicates the version number. A chinese version is available.
0 Motivation
0.1 enigma system
This library is a result of developing the enigma system. To understand this library, the enigma system is firstly briefly described. Enigma system is designed as a service running on embedded systems:
- For requests inputed from the outside of the embedded system via API, it
does following operations:
- display the request to the user, ask for approval, encrypt and/or sign the data within the request, and return the result to the outside.
- treat the input as ciphertext or signed plaintext from another enigma, decrypt the content and validate it, display the result to the user, and return the result to the outside under user approval.
- under request and user approval, transfer the identity public key to the outside.
- under request and user approval, improve an identity public key from other user.
- Besides, users are able to do following operations with interacting with IO
devices connected to the embedded system, e.g. keyboards, mouses, screens:
- manage(including listing, generating, deleting, importing, exporting) the stored identity public keys(public and secret) within the embedded system.
- edit the drafts for sending. When approving requests from the outside, use drafts to replace given data.
- view the received and stored decrypted data.
- edit and save a type of special drafts, which is the signature on the dentity keys of the others(named token).
Engima system serves as the core component of many communicating tools and protocols. For example, in cooperation with plugins in instant messaging softwares, it provides peer-to-peer(p2p) encryption. It may also works as an component of automatic doors. By signing the given challenge, user proves its identity to the door.
0.2 neoatlantis-crypto-js library
In developing the engima system, the author realized, that it is necessary to separate the cryptographic part of this system into one library, in which features like secure random generators, symmetric encryptions, asymmetric encryptions and signatures, hash functions are covered. The library covers also 2 data structures for constructing the engima system: identity key and message.
Another highlight point of this library is, considering that even though these 2 data structures are included, it is still not so easy to construct an enigma system since one have to deal with storage and the complex logic reading the ciphertext, there is an interface, which is in fact a nearly complete implementation of the enigma system. The job of programmers are therefore now only to ask the user with user interface with questions emitted from this library, and the output is there. Using this implementation, the programmer shall construct a complete or reduced engima system, retaining the compatibility.
In short, the neoatlantis-crypto-js library provides user not only the basic features doing cryptography, but also the rich logic prepared for peer-to-peer encryption.
0.3 list of features
Here is a complete list of features shipped together with this library.
- Hashing
- with algorithms of BLAKE2s、WHIRLPOOL、RIPEMD160,
- and use them to calculate MAC(Message Authentication Code) of some message,
- and use them to do PBKDF2 key derivation. There is however speed limitations.
- Encryption / Signature
- symmetrically. This library ships AES and Salsa20/20, but they are not exposed to end user. The symmetric cipher is constructed based upon these algorithms but there is more.
- asymmetrically. There is an implementation within this library of asymmetric ciphers, that is not compatible with any existing standards. These ciphers utilize ECDH and ECDSA. Beginning from the basic seed secret, it derives secrets for initializing ECDH and ECDSA. Notice that the ECDH are primarily used for exchanging keys. In our library, the public key used in ECDH is the public key, the peer key is attached to the ciphertext for constructing shared secrets, which is laterly used for encrypting the actually payload.
- enigma system
- A data structure of identity key. Identity key is constructed with a subject within 256 characters, a public key and a self-signature on the subject and the public key.
- A message data structure. A message data structure is either a payload,
or an envelope carrying an encrypted payload and information for
decryption.
- Payload carries the signer, signature and the plaintext.
- Envelope carries the encrypted payload, the decryptor(fingerprint), and information for the decryptor(s) to recover the key, and a sign of compression.
- An abstract implementation of the enigma system
- Functionalities are called via API
- Functionalities are called using Q&A, parameters are asked, and exceptions are thrown.
- The user uses only a few logic to forward the question to the user, or answer them by themselve, and the task is completed.
- The storage, based on interfaces the same of localStorage, are managed by the implementation. The user needs only to maintain a localStorage.
- Acceleration modules
- based upon platforms running this library, these modules replaces some native features with interfaces provided by the platform.
- for example, use the
crypto
library from NodeJS to hash, or use the interface on a customized browser to do random numbers more securely.
- Utilities
- convert between JavaScript Strings, ASCII Strings、Base32、Base64 and HEX.
- operate on ArrayBuffer, to concat, do XOR, test if equal, and reverse.
- generate random bytes.
- generate UUID.
- test variable types.
- (not complete) LZW compression.
- serialize and unserialize JSON with predefined data structures.
1 Basic knowledgements
1.1 Initializing the neoatlantis-crypto-js library
To initialize the library in NodeJS, use:
Use RequireJS in browsers:
1.2 Remarks
Most of the time, the IOs are in format of ArrayBuffer
.
To read its content, use code like followings:
To construct an ArrayBuffer
:
2 Use the utilities
Utilities as functions are provided under crypto.util
. They are also used
by our library itself.
2.1 Obtain random data
The most common variant is obtaining random bytes of length n:
You may use following code to make the data more random.
Using touch
function, you affect the internal state of the random generator
globally, although you have used new
to generate rand
. The random data are
more unpredictable after that. You can bind this event to user inputs like
mouse moves and keyboard types. The time of calling touch
affects the random
generator, not the type of your choosen input.
2.2 Dealing with ArrayBuffer
Use crypto.util.buffer
, and process the ArrayBuffer
type of data.
XOR. To xor 2 ArrayBuffer
s with same byteLength
, use following code:
Concatenation. To get a comprised ArrayBuffer
, use following code:
If equal. To see if 2 ArrayBuffer
are equal in content, use following code:
Reverse. Swap the first item with the last item, the second with the one before last, etc.
2.3 Test variable type
Use following code, to test if a variable is of a given type:
2.4 Encoding conversion
Use following code, to get a encoded output, with an input of a string or ArrayBuffer
.
2.5 UUID generation
Use following code, to get an UUID out of crypto.util.srand
.
2.6 Serialization and De-serialization
This library contains a serialization tool dealing with structured data. You may define a template of a structure, and fill with data, to get the serialized output, or reverse.
The input to be serialized is an Object in javascript. In the serialized result, keys are not included. That is to say, you have to know previously, which template may be used to deserialize the data.
You have following choices to define a data structure:
- binary, byte streams.
shortBinary
,binary
,longBinary
are supported.shortBinary
takes one byte to save the length of stream, saves at most 255 bytes, and can be null.binary
takes 2 bytes, saves at most 65535 bytes, allows null.longBinary
takes 4 bytes, saves at most 2^32-1 bytes(about 4GB), can be null.
boolean
, a boolean variable, takes one byte.constant
, sets a givenArrayBuffer
into the serialized result. Takes the same length ofconstant
in bytes. In deserialization, it will be asserted that a same field exists in the input, or an exception will be thrown.enum
, takes a selection of a predefined list. The list contains at most 255 items. Takes one byte in serialized result. During deserialization, the result will be translated into one string in the enumerate list.datetime
, takes a Date object as input. The resolution is one second. Takes 7 bytes in the result. Can record years from 0 to 65535.- array, an array of binaries. Supports
shortArray
,array
, respectively 255 and 65535 items. CAUTION:shortArray
stores onlyshortBinary
, andarray
stores onlybinary
type.
2.6.1 Define a template for serialization
Use following code to define a serializing template:
template
is a javascript Object
. In the example, _
, subject
, algorithm
,
public
, secret
, signature
are defined. When this template is used in
generating result, use such keys. But the key names are excluded from final
result.
Notice that algorithm
is a enum type. _
is a constant.
Therefore when deserializing, it will be asserted that at _
a defined value
could be read. This is used to distinguish an expected data structure at first
glance.
2.6.2 To serialize a piece of data
Use following code to serialize:
2.6.3 To deserialize a piece of data
Use following code, to deserialize from a given ArrayBuffer
:
Remember to handle possible exceptions during deserialization with try-catch. This happens when the data doesn’t match a given template.
3 Hashing
The library provides an interface for doing hashes on data, for calculating MAC(Message Authentication Code), and for doing a slow PBKDF2. The internal implementation is done with Whirlpool algorithm, the output length can be configured from 1 byte to 64 bytes.
3.1 Calculate hashes of an ArrayBuffer
Use following code to calculate hashes of an ArrayBuffer
:
3.2 Calculate MAC of an ArrayBuffer
MAC(Message Authentication Code) is another hash with an external key input. You may use it to authenticate the integrity of a message against a known key.
3.3 PBKDF2 key derivation
PBKDF2 key derivation consumes computing resources, and can make a stronger key basing on a weaker password(from user input, for example). This may be useful against brute forces, or to improve the quality(randomness) of the key.
PBKDF2 algorithms calls internally MAC algorithms, as specified in 3.2. In such calls, the MAC algorithm are not configured to slice its results, they are called with full bytes output.
4 Symmetric Encryptions
This library provides a single symmetric encryption interface. By entering the key, the data can be encrypted or decrypted. ArrayBuffer of any length can be treated.
The symmetric encryption algorithm contains following steps. Take encryption as an example:
- choose a 10-bit salt
- derive intermediate keys from key at input for all actual algorithms
- use hash algorithms to calculate the MAC of plaintext(against a key produced in previous step) with a length of 6 bytes. It will be attached to plaintext at beginning.
- pad the result plaintext into a length which is the multiple of 16 bytes(RFC5652).
Then encrypt it first using
Salsa20/20
, thenAES-128-ECB
.
Since Salsa20/20
is used, using AES-128
in ECB mode will not introduce its
security flaws. MAC is used to validate if a successful decryption.
To use symmetric encryptions:
To decrypt:
5 Asymmetric Encryptions and Signatures
The asymmetric algorithms provides by this library are in suites. Their implementations and output formats are not obeying any existing standards.
Each suite of algorithm has a name. By specifying this name, you are specifying the actual algorithms for encryptions and signatures(as well as their parameters), and the hash algorithm before doing a signature. Thanks to this definition, each algorithm can be used to both encryption and signature.
And, as by design to ease the secret key management, the secret key of a user is always some random bytes that can be assigned outside the library. By specifying the secret key and the algorithm, the public key can be derived.
Currently supported suites of algorithms are listed as follows:
Name | Encrypt Algorithm | Sign Algorithm | Hash Algorithm | Private Key Length(bit) |
---|---|---|---|---|
NECRAC256 | ECDH/secp521r1 | ECDSA/secp521r1 | WHIRLPOOL | 64 x 8 = 512 |
NECRAC192 | ECDH/secp384r1 | ECDSA/secp384r1 | BLAKE2s | 48 x 8 = 384 |
NECRAC128 | ECDH/secp256k1 | ECDSA/secp256r1 | BLAKE2s | 32 x 8 = 256 |
NECRAC112 | ECDH/secp224r1 | ECDSA/secp224r1 | RIPEMD160 | 28 x 8 = 224 |
NECRAC96 | ECDH/secp192r1 | ECDSA/secp192k1 | RIPEMD160 | 24 x 8 = 192 |
By selecting any suite and initializing the asymmetric cipher instance, random inputs can be proceeded, aka be encrypted/decrypted/signed/verified.
5.1 Initializing an asymmetric cipher instance
使用如下代码,根据选取的算法套装,初始化一个不对称加密的对象。
在初始化之后,asym
有2个方法可供进一步调用,即setPrivateKey
和setPublicKey
。
setPrivateKey
用来设定一个私钥。其参数是被随机的字节填充的ArrayBuffer
类型的数据,长度由上文表格确定。
setPublicKey
用来设定一个公钥,由下文所述的导出公钥的方式获取,其变量类型也是ArrayBuffer
。
调用这2个方法之一后,asym
将会暴露其他的方法以供进一步操作。
同时,asym
将删去已经暴露的setPrivateKey
和setPublicKey
方法,禁止重新进行初始化操作。
5.2 使用不对称加密对象
设定了公钥或者私钥之后的asym
对象,可以被用来进行如下操作:
5.2.1 推导公钥
在指定了一个任意的私钥之后,使用算法推导给出对应于这个私钥的公钥。 之后这个公钥就可以被公开,以便让他人藉此向私钥持有者加密发送消息,或者公钥持有者验证私钥持有者签署过的消息。
为此,asym
对象必须是通过setPrivateKey
方法初始化的。
5.2.2 加密
使用加密方法,可以在得到别人的公钥之后,向他发送加密的数据。只有持有和公钥对应的私钥的人才能解密数据,其他人不能。
使用setPrivateKey
和setPublicKey
方法通过输入私钥或者公钥初始化的asym
对象都可以调用这一功能。
5.2.3 解密
已知一个私钥,解密别人发来的数据。
只有使用setPrivateKey
方法通过私钥初始化的asym
对象可以调用这一功能。
5.2.4 签名
签名的功能是对给定的plaintextBuf
输入,生成一段数据,其他人拥有公钥的时候,可以据此确定确实是私钥的拥有者进行了“签名”这一操作,而不是任何其他人。
此外,签名者自己不能否定自己曾经签署过这段数据。
只有使用setPrivateKey
方法通过私钥初始化的asym
对象可以调用这一功能。
5.2.5 验证签名
在得到别人的公钥之后,可以验证此人所签署过的数据。需要输入声称被此人签署过的数据plaintextBuf2
和签名数据signatureBuf2
。
使用setPrivateKey
和setPublicKey
方法通过输入私钥或者公钥初始化的asym
对象都可以调用这一功能。
但是自然,一般在实际应用中,有意义的用法是通过输入公钥进行的初始化。