Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
crypto: bring module into modern age
Browse files Browse the repository at this point in the history
Introduce 'buffer' encoding, allow returning and giving buffers as
arguments of 'crypto' routines.

Fix #3278
  • Loading branch information
indutny committed Sep 18, 2012
1 parent 3301c90 commit 63ff449
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 142 deletions.
51 changes: 27 additions & 24 deletions doc/api/crypto.markdown
Expand Up @@ -64,14 +64,14 @@ Returned by `crypto.createHash`.
### hash.update(data, [input_encoding])

Updates the hash content with the given `data`, the encoding of which is given
in `input_encoding` and can be `'utf8'`, `'ascii'` or `'binary'`.
in `input_encoding` and can be `'buffer'`, `'utf8'`, `'ascii'` or `'binary'`.
Defaults to `'binary'`.
This can be called many times with new data as it is streamed.

### hash.digest([encoding])

Calculates the digest of all of the passed data to be hashed.
The `encoding` can be `'hex'`, `'binary'` or `'base64'`.
The `encoding` can be `'buffer'`, `'hex'`, `'binary'` or `'base64'`.
Defaults to `'binary'`.

Note: `hash` object can not be used after `digest()` method been called.
Expand All @@ -98,7 +98,7 @@ This can be called many times with new data as it is streamed.
### hmac.digest([encoding])

Calculates the digest of all of the passed data to the hmac.
The `encoding` can be `'hex'`, `'binary'` or `'base64'`.
The `encoding` can be `'buffer'`, `'hex'`, `'binary'` or `'base64'`.
Defaults to `'binary'`.

Note: `hmac` object can not be used after `digest()` method been called.
Expand Down Expand Up @@ -134,18 +134,18 @@ Returned by `crypto.createCipher` and `crypto.createCipheriv`.
### cipher.update(data, [input_encoding], [output_encoding])

Updates the cipher with `data`, the encoding of which is given in
`input_encoding` and can be `'utf8'`, `'ascii'` or `'binary'`.
`input_encoding` and can be `'buffer'`, `'utf8'`, `'ascii'` or `'binary'`.
Defaults to `'binary'`.

The `output_encoding` specifies the output format of the enciphered data,
and can be `'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`.
and can be `'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`.

Returns the enciphered contents, and can be called many times with new data as it is streamed.

### cipher.final([output_encoding])

Returns any remaining enciphered contents, with `output_encoding` being one of:
`'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`.
`'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`.

Note: `cipher` object can not be used after `final()` method been called.

Expand Down Expand Up @@ -174,16 +174,18 @@ Returned by `crypto.createDecipher` and `crypto.createDecipheriv`.

### decipher.update(data, [input_encoding], [output_encoding])

Updates the decipher with `data`, which is encoded in `'binary'`, `'base64'`
or `'hex'`. Defaults to `'binary'`.
Updates the decipher with `data`, which is encoded in `'buffer'`, `'binary'`,
`'base64'` or `'hex'`. Defaults to `'binary'`.

The `output_decoding` specifies in what format to return the deciphered
plaintext: `'binary'`, `'ascii'` or `'utf8'`. Defaults to `'binary'`.
plaintext: `'buffer'`, `'binary'`, `'ascii'` or `'utf8'`.
Defaults to `'binary'`.

### decipher.final([output_encoding])

Returns any remaining plaintext which is deciphered,
with `output_encoding` being one of: `'binary'`, `'ascii'` or `'utf8'`.
with `output_encoding` being one of: `'buffer'`, `'binary'`, `'ascii'` or
`'utf8'`.
Defaults to `'binary'`.

Note: `decipher` object can not be used after `final()` method been called.
Expand Down Expand Up @@ -216,8 +218,8 @@ This can be called many times with new data as it is streamed.
Calculates the signature on all the updated data passed through the signer.
`private_key` is a string containing the PEM encoded private key for signing.

Returns the signature in `output_format` which can be `'binary'`, `'hex'` or
`'base64'`. Defaults to `'binary'`.
Returns the signature in `output_format` which can be `'buffer'`, `'binary'`,
`'hex'` or `'base64'`. Defaults to `'binary'`.

Note: `signer` object can not be used after `sign()` method been called.

Expand All @@ -242,8 +244,8 @@ This can be called many times with new data as it is streamed.
Verifies the signed data by using the `object` and `signature`. `object` is a
string containing a PEM encoded object, which can be one of RSA public key,
DSA public key, or X.509 certificate. `signature` is the previously calculated
signature for the data, in the `signature_format` which can be `'binary'`,
`'hex'` or `'base64'`. Defaults to `'binary'`.
signature for the data, in the `signature_format` which can be `'buffer'`,
`'binary'`, `'hex'` or `'base64'`. Defaults to `'binary'`.

Returns true or false depending on the validity of the signature for the data and public key.

Expand All @@ -257,7 +259,8 @@ given bit length. The generator used is `2`.
## crypto.createDiffieHellman(prime, [encoding])

Creates a Diffie-Hellman key exchange object using the supplied prime. The
generator used is `2`. Encoding can be `'binary'`, `'hex'`, or `'base64'`.
generator used is `2`. Encoding can be `'buffer'`, `'binary'`, `'hex'`, or
`'base64'`.
Defaults to `'binary'`.

## Class: DiffieHellman
Expand All @@ -278,19 +281,19 @@ Defaults to `'binary'`.
Computes the shared secret using `other_public_key` as the other party's
public key and returns the computed shared secret. Supplied key is
interpreted using specified `input_encoding`, and secret is encoded using
specified `output_encoding`. Encodings can be `'binary'`, `'hex'`, or
`'base64'`. The input encoding defaults to `'binary'`.
specified `output_encoding`. Encodings can be `'buffer'`, `'binary'`, `'hex'`,
or `'base64'`. The input encoding defaults to `'binary'`.
If no output encoding is given, the input encoding is used as output encoding.

### diffieHellman.getPrime([encoding])

Returns the Diffie-Hellman prime in the specified encoding, which can be
`'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.
`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.

### diffieHellman.getGenerator([encoding])

Returns the Diffie-Hellman prime in the specified encoding, which can be
`'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.
`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.

### diffieHellman.getPublicKey([encoding])

Expand All @@ -300,17 +303,17 @@ be `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.
### diffieHellman.getPrivateKey([encoding])

Returns the Diffie-Hellman private key in the specified encoding, which can
be `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.
be `'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`.

### diffieHellman.setPublicKey(public_key, [encoding])

Sets the Diffie-Hellman public key. Key encoding can be `'binary'`, `'hex'`,
or `'base64'`. Defaults to `'binary'`.
Sets the Diffie-Hellman public key. Key encoding can be `'buffer', ``'binary'`,
`'hex'` or `'base64'`. Defaults to `'binary'`.

### diffieHellman.setPrivateKey(public_key, [encoding])

Sets the Diffie-Hellman private key. Key encoding can be `'binary'`, `'hex'`,
or `'base64'`. Defaults to `'binary'`.
Sets the Diffie-Hellman private key. Key encoding can be `'buffer'`, `'binary'`,
`'hex'` or `'base64'`. Defaults to `'binary'`.

## crypto.getDiffieHellman(group_name)

Expand Down
12 changes: 10 additions & 2 deletions src/node.cc
Expand Up @@ -1067,6 +1067,8 @@ enum encoding ParseEncoding(Handle<Value> encoding_v, enum encoding _default) {
return UCS2;
} else if (strcasecmp(*encoding, "binary") == 0) {
return BINARY;
} else if (strcasecmp(*encoding, "buffer") == 0) {
return BUFFER;
} else if (strcasecmp(*encoding, "hex") == 0) {
return HEX;
} else if (strcasecmp(*encoding, "raw") == 0) {
Expand All @@ -1089,6 +1091,11 @@ enum encoding ParseEncoding(Handle<Value> encoding_v, enum encoding _default) {
Local<Value> Encode(const void *buf, size_t len, enum encoding encoding) {
HandleScope scope;

if (encoding == BUFFER) {
return scope.Close(
Buffer::New(static_cast<const char*>(buf), len)->handle_);
}

if (!len) return scope.Close(String::Empty());

if (encoding == BINARY) {
Expand Down Expand Up @@ -1119,7 +1126,7 @@ ssize_t DecodeBytes(v8::Handle<v8::Value> val, enum encoding encoding) {
return -1;
}

if (encoding == BINARY && Buffer::HasInstance(val)) {
if ((encoding == BUFFER || encoding == BINARY) && Buffer::HasInstance(val)) {
return Buffer::Length(val->ToObject());
}

Expand Down Expand Up @@ -1158,7 +1165,8 @@ ssize_t DecodeWrite(char *buf,

bool is_buffer = Buffer::HasInstance(val);

if (is_buffer && encoding == BINARY) { // fast path, copy buffer data
if (is_buffer && (encoding == BINARY || encoding == BUFFER)) {
// fast path, copy buffer data
const char* data = Buffer::Data(val.As<Object>());
size_t size = Buffer::Length(val.As<Object>());
size_t len = size < buflen ? size : buflen;
Expand Down
2 changes: 1 addition & 1 deletion src/node.h
Expand Up @@ -127,7 +127,7 @@ void SetPrototypeMethod(target_t target,
#define NODE_SET_METHOD node::SetMethod
#define NODE_SET_PROTOTYPE_METHOD node::SetPrototypeMethod

enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX};
enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER};
enum encoding ParseEncoding(v8::Handle<v8::Value> encoding_v,
enum encoding _default = BINARY);
NODE_EXTERN void FatalException(v8::TryCatch &try_catch);
Expand Down

14 comments on commit 63ff449

@mscdex
Copy link

@mscdex mscdex commented on 63ff449 Sep 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the default be 'buffer' instead of 'binary' if no encoding is given and the argument is a Buffer?

@langpavel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mscdex I think it might break backward compatibility.

@mscdex
Copy link

@mscdex mscdex commented on 63ff449 Sep 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine.... this is in 0.9/master already because it's an API change :-)

@TooTallNate
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

I was under the impression that this "buffer encoding" was going to be internal, and not exposed in the docs. Returning a Buffer instance should be the default, just like all the other Node core APIs IMO.

@langpavel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TooTallNate @mscdex:
All crypto is stable

3 - Stable The API has proven satisfactory, but cleanup in the underlying code may cause minor changes.
Backwards-compatibility is guaranteed.

This doesn't mean that we can change stable API between major releases, or I'm wrong?

By changing default behaviour you break lot of modules. (need investigation)

Note that there is more features required from crypto e.g. streams.

One posibility is entirely new module (something like crypto.2) which will respect all the new goods.

@mscdex
Copy link

@mscdex mscdex commented on 63ff449 Sep 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO if "stable" here means that this particular change cannot be made, then I think it was a mistake to label it as "stable", especially when Buffer support wasn't even added until just now (which is crazy).

Also as @TooTallNate mentioned, it's better to have Buffer as a default encoding to more closely match encoding behavior elsewhere in node. The less gotchas the better.

@langpavel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mscdex Absolutely agree, but...

  • Step one - deprecate binary encoding, which is now implicit in this API. This will come in Deprecate 'binary' encoding #3279 (@isaacs?)
  • Step two - cut out binary encoding entirely and introduce Buffer as implicit, deprecate other encodings in crypto module.

but step two must come some time later, giving maintainers of packages a chance to update

@mscdex
Copy link

@mscdex mscdex commented on 63ff449 Sep 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As much as I agree that the 'binary' encoding is not very efficient/elegant/whatever, it's very nice to have available because you can use native string functions to find bytes, perform manipulations, etc.

IMHO since Buffer does not have these same kinds of methods available (unless core members would be willing to merge in something like node-buffertools), we should not remove 'binary' encoding support.

Also, I'm not so sure that deprecating additional/other encodings in crypto is a good idea (making a Buffer the only allowed input/output?). Especially if we're really talking about just crypto, since that would introduce more gotchas if encodings can be used in similar situations elsewhere in node.

@mscdex
Copy link

@mscdex mscdex commented on 63ff449 Sep 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richardhoffman that's like telling people to just convert binary string results back to a Buffer when using crypto functions. Sure you can do that, but why would you want to do it?

@langpavel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mscdex Where input or output has no encoding by nature - just bytes - there shouldn't be string input or output.
Speaking in C++ types, void * and char * are different. No reason to reinterpret_cast them in javascript.

Node Buffer is not part of standart nor V8, that means pull requests can be accepted if reasonable.

If you want text, you can get if from buffer by Buffer#toString(encoding, start, end) and you should choose proper one or 'hex'

@yi
Copy link

@yi yi commented on 63ff449 Sep 19, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to @mscdex and @TooTallNate : it's better to have Buffer as a default encoding to more closely match encoding behavior elsewhere in node.

To @richardhoffman: Why would you need to modify a crypto buffer?
Because:

  1. computers talk in binary, not talk in human language
  2. String in JavaScript is UTF friendly, which is handy to write program logic, but is a waster in data processing.

@isaacs
Copy link

@isaacs isaacs commented on 63ff449 Sep 20, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, here's a plan. @indutny et al, please let me know if you think this is sane:

  1. Take this patch, basically as-is. binary remains the default, and we add buffer, noting that it is kludgey and weird.
  2. Reduce the crypto stability index to 2: Unstable. Add a paragraph in the docs noting that the binary encoding will cease to be the default in a future version.
  3. Release 0.10.
  4. In 0.11.0, make buffer the default, and bump the stability back up to 3: Stable.

@TooTallNate
Copy link

@TooTallNate TooTallNate commented on 63ff449 Sep 20, 2012 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@langpavel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isaacs SGTM too

Please sign in to comment.