Skip to content

crypto: refactor crypto module - #11138

Closed
jasnell wants to merge 1 commit into
nodejs:masterfrom
jasnell:crypto-refactor-cleanup
Closed

jasnell wants to merge 1 commit into
nodejs:masterfrom
jasnell:crypto-refactor-cleanup

Conversation

@jasnell

@jasnell jasnell commented Feb 3, 2017

Copy link
Copy Markdown
Member

Refactor the crypto module to make the code more readable and maintainable. Leverages ES6 classes. No test changes are required. Existing code should keep working.

Provide improved inspect support for crypto classes.

Fix bug in ECDH.prototype.getPublicKey

No changes to tests are required.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

crypto

@nodejs-github-bot nodejs-github-bot added build Issues and PRs related to Node.js builds or CI infrastructure. crypto Issues and PRs related to the crypto subsystem. labels Feb 3, 2017
@jasnell

jasnell commented Feb 3, 2017

Copy link
Copy Markdown
Member Author

btw, side note to @trevnorris and @addaleax this demonstrates one of the methods we can use to evolve Buffer into an ES6 style class while maintaining backwards API compat

@mhdawson

mhdawson commented Feb 3, 2017

Copy link
Copy Markdown
Member

@sam-github

@jasnell

jasnell commented Feb 3, 2017

Copy link
Copy Markdown
Member Author

@Trott

Trott commented Feb 5, 2017

Copy link
Copy Markdown
Member

Probably warrants running benchmarks to compare before/after perf?

@jasnell

jasnell commented Feb 6, 2017

Copy link
Copy Markdown
Member Author

Also needs a new CI run but will hold off until the OSX build bot is back up

Comment thread lib/internal/crypto/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we convert these to use the module.exports = { ... } format instead? The reason is that at least exports.DEFAULT_ENCODING is accessed quite frequently in the crypto code here and having a faster property lookup would be beneficial.

@mscdex

mscdex commented Feb 6, 2017

Copy link
Copy Markdown
Contributor

Why does the code need to move to the internal/ directory?

@mscdex

mscdex commented Feb 6, 2017

Copy link
Copy Markdown
Contributor

Also, I agree that we need to have some solid benchmark results for these changes.

@jasnell

jasnell commented Feb 6, 2017

Copy link
Copy Markdown
Member Author

Why does the code need to move to the internal/ directory

@mscdex ... Organization mostly, I'd like to separate the various related classes into separate files under lib/internal/crypto/. Also, it separates the ES6 class implementation from the external facing API (e.g. we cannot have both a Hash function and a Hash class in the same js file).

@mscdex

mscdex commented Feb 6, 2017

Copy link
Copy Markdown
Contributor

@jasnell If it's unavoidable then that's ok. I just don't want to start stuffing things in internal/ unless it's necessary, since I thought the idea behind internal/ was mostly to house things that shouldn't be (or shouldn't have been) exposed to end users.

@jasnell

jasnell commented Feb 6, 2017 •

Copy link
Copy Markdown
Member Author

Here are the benchmarks comparing this PR to 7.4.0: https://gist.github.com/jasnell/fe734fdc57ca143daf6c43c2cc1cb95b

The results appear to show no significant change in most cases and significant improvement in others.

@jasnell

jasnell commented Feb 6, 2017

Copy link
Copy Markdown
Member Author

@mscdex ... updated to use the module.exports = { ... } pattern as suggested.

@jasnell

jasnell commented Feb 6, 2017

Copy link
Copy Markdown
Member Author

@jasnell

jasnell commented Feb 9, 2017

Copy link
Copy Markdown
Member Author

Ping @nodejs/crypto

@mscdex

mscdex commented Feb 9, 2017

Copy link
Copy Markdown
Contributor

I'd like to run the benchmarks on this first.

@jasnell

jasnell commented Feb 9, 2017

Copy link
Copy Markdown
Member Author

@mscdex ... definitely. I did one run on the benchmarks here: https://gist.github.com/jasnell/fe734fdc57ca143daf6c43c2cc1cb95b

Please let me know if these align with the results you get.

@sam-github sam-github left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm generally a favour of giant files being broken up, but this just seems to move all the contents from lib/crypto.js to lib/internal/crypto/crypto.js, is it a first step to dividing the classes into their own files?

Comment thread lib/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Its semver-major when a property gets replaced by a pair of accessor functions, isn't it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I thought I had already labeled it that way but looks like I missed it. Added!

Comment thread lib/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't this a type change? Before, typeof crypto.createHash() === crypto.Hash, but now it doesn't? Why not exports.createHash = exports.Hash = internalCrypto.Hash;?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is the setHasInstance() supposed to fixup the types after the fact?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The setHasInstance() allows instanceof to continue working. crypto.createHash() === crypto.Hash should still work here. The two are still aliases of one another. Both will return an instance of the internal Hash class, and the setHasInstance() sets it up so that instanceof keeps working as expected.

Comment thread lib/internal/crypto/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please sort the require lines

Comment thread lib/internal/crypto/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

its hard to tell, are there any changes in this code, or is just being moved?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved and refactored in the ES6 class. There are no functional changes with the exception of a small bug fix.

@jasnell

jasnell commented Feb 9, 2017

Copy link
Copy Markdown
Member Author

is it a first step to dividing the classes into their own files?

Yes. The idea here is to ease the maintenance burden over time, but in chunks rather than all at once.

@jasnell jasnell added the semver-major PRs that contain breaking changes and should be released in the next major version. label Feb 9, 2017
@sam-github

Copy link
Copy Markdown
Contributor

Given that all the code is being removed from lib/crypto.js, so that no PR touching that file after this change can backport past it, and a split of lib/internal/crypto.js into more small files will create a similar backport barrier, I'd favour doing the split. Its mostly textual, I'd think, the big file is just a series of indepenent code, well suited to being broken up.

I'm a bit worried about the backport barrier, but I guess we can't ever improve the code base if we don't bit some bullets. The bullet would be more palatable if the move to internal reduced the API surface exposed to node users, but I don't think crypto has any unnecessary API surface (other than _toBuf?) to remove.

@jasnell

jasnell commented Feb 9, 2017

Copy link
Copy Markdown
Member Author

+1... I was going to suggest deprecating the exposed _toBuf() so that can make that fully internal. Everything else seems pretty stable -- it would be difficult to minimize it further.

@sam-github

Copy link
Copy Markdown
Contributor

Fix bug in ECDH.prototype.getPublicKey

What is bug? I can't find it. I don't think a commit should both move code, and fix bugs, its not possible to see the bug-fixing code change in the diff, and the commit message doesn't mention a bug fix, though the PR description does.

Is it possible to PR the bug fix seperately, so it can be backported and visible in history?

@jasnell any comment on my last comment? I prefer one break-any-backports event, rather than two: why not break lib/internal/crypto.js up immediately into the file-per-algorithm that is intended?

@sam-github

Copy link
Copy Markdown
Contributor

Other than my two concerns above, I'm in favour of this (in case it wasn't clear).

@sam-github

Copy link
Copy Markdown
Contributor

I also like the idea of deprecating _toBuf (in a follow-on PR), so it can be deprecated in 8.x, and removed in a subsequent major.

@jasnell

jasnell commented Feb 10, 2017

Copy link
Copy Markdown
Member Author

@sam-github ... splitting up the files is reasonable. I'll update this PR with that change.

The bug fixed is minor, see https://github.com/nodejs/node/pull/11138/files#diff-437b078f0e3aa5b13790315b3813fd4fL518

Basically, there's an argument that was being checked to see if the value was a number, but then the check was ignored after. It looks like there was an intent at some point to support number arguments but it wasn't completed. I checked the documentation and passing in a number for the argument is not described.

@jasnell
jasnell force-pushed the crypto-refactor-cleanup branch from 009c3c8 to ad006bf Compare February 10, 2017 22:09
@jasnell

jasnell commented Feb 10, 2017

Copy link
Copy Markdown
Member Author

@sam-github ... updated with the files split out.

Comment thread lib/crypto.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we change these to use the module.exports = { ... } format instead?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As well as any other (similar) pre-existing uses of exports.* = ...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I suppose we could. It would require quite a bit more moving things around but it's not out of the question

Comment thread lib/internal/crypto/cipher.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On an unrelated note.... I think we should deprecate this (along with the same in Decipheriv). I don't know why this was ever added and it's not even documented.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, I was planning on doing so in a separate follow-up PR.

@jasnell
jasnell force-pushed the crypto-refactor-cleanup branch from 0c4aad1 to 995a6b4 Compare February 14, 2017 22:50
@jasnell

jasnell commented Feb 14, 2017

Copy link
Copy Markdown
Member Author

@mscdex ... ok, refactored to use the exports = { ... } pattern as requested.

Comment thread lib/crypto.js Outdated

@mscdex mscdex Feb 14, 2017 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can add these special properties during module.exports creation by using Object.create() instead with all properties.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Would doing so give any perf advantage?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well, the point of doing the module.exports = { ... } is to avoid hidden classes, so these last few Object.defineProperties() are creating hidden classes AFAIK (akin to what was being done before -- exports.foo = bar).

@jasnell

jasnell commented Feb 15, 2017

Copy link
Copy Markdown
Member Author

@mscdex ... updated with the Object.create suggestion!

Comment thread lib/crypto.js Outdated

@mscdex mscdex Feb 15, 2017 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry, I meant that these properties should go in the object passed as the second argument. This first argument is for defining the prototype, the second is for defining on the object itself.

For the prototype argument, I guess we should use Object.prototype or whatever to be on the safe side, although I would've preferred null.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I reverted that last change... not sure I'm +1 on doing it the way you're suggesting. Will think about it some more

@jasnell
jasnell force-pushed the crypto-refactor-cleanup branch from 556e22e to 49ec8ac Compare February 15, 2017 05:48
@jasnell jasnell added the wip Issues and PRs that are still a work in progress. label Feb 15, 2017
@jasnell
jasnell force-pushed the crypto-refactor-cleanup branch from 49ec8ac to 629ea29 Compare February 17, 2017 19:10
@jasnell jasnell removed the wip Issues and PRs that are still a work in progress. label Feb 17, 2017
@jasnell

jasnell commented Feb 17, 2017 •

Copy link
Copy Markdown
Member Author

@nodejs/ctc @nodejs/crypto @sam-github ... updated this and rebased. PTAL

@jasnell

jasnell commented Feb 17, 2017

Copy link
Copy Markdown
Member Author

@jasnell

jasnell commented Feb 17, 2017

Copy link
Copy Markdown
Member Author

Failure in CI is unrelated.

@jasnell

jasnell commented Feb 17, 2017

Copy link
Copy Markdown
Member Author

some benchmark results after refactor:

james@ubuntu:~/node/node$ cat ~/cipherbench | Rscript benchmark/compare.R
                                                                                        improvement confidence      p.value
 crypto/cipher-stream.js api="legacy" len=1024 type="asc" cipher="AES192" writes=500         3.02 %            6.569701e-01
 crypto/cipher-stream.js api="legacy" len=1024 type="asc" cipher="AES256" writes=500         0.55 %            9.160192e-01
 crypto/cipher-stream.js api="legacy" len=1024 type="buf" cipher="AES192" writes=500        44.65 %        *** 1.704893e-08
 crypto/cipher-stream.js api="legacy" len=1024 type="buf" cipher="AES256" writes=500        32.21 %        *** 1.393279e-05
 crypto/cipher-stream.js api="legacy" len=1024 type="utf" cipher="AES192" writes=500        -6.45 %            2.780600e-01
 crypto/cipher-stream.js api="legacy" len=1024 type="utf" cipher="AES256" writes=500         3.77 %            5.375893e-01
 crypto/cipher-stream.js api="legacy" len=102400 type="asc" cipher="AES192" writes=500      10.76 %          * 2.964838e-02
 crypto/cipher-stream.js api="legacy" len=102400 type="asc" cipher="AES256" writes=500       7.34 %            6.618673e-02
 crypto/cipher-stream.js api="legacy" len=102400 type="buf" cipher="AES192" writes=500       6.32 %            1.239321e-01
 crypto/cipher-stream.js api="legacy" len=102400 type="buf" cipher="AES256" writes=500       5.75 %            7.915074e-02
 crypto/cipher-stream.js api="legacy" len=102400 type="utf" cipher="AES192" writes=500       2.51 %            6.109682e-01
 crypto/cipher-stream.js api="legacy" len=102400 type="utf" cipher="AES256" writes=500       4.26 %            3.443735e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="asc" cipher="AES192" writes=500      1.31 %            7.089094e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="asc" cipher="AES256" writes=500     -0.70 %            8.473155e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="buf" cipher="AES192" writes=500     -1.93 %            5.414937e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="buf" cipher="AES256" writes=500     -1.64 %            6.342640e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="utf" cipher="AES192" writes=500     -0.45 %            8.916841e-01
 crypto/cipher-stream.js api="legacy" len=1048576 type="utf" cipher="AES256" writes=500      2.15 %            4.649651e-01
 crypto/cipher-stream.js api="legacy" len=2 type="asc" cipher="AES192" writes=500           91.17 %        *** 6.031314e-14
 crypto/cipher-stream.js api="legacy" len=2 type="asc" cipher="AES256" writes=500           69.03 %        *** 3.420023e-09
 crypto/cipher-stream.js api="legacy" len=2 type="buf" cipher="AES192" writes=500           75.51 %        *** 7.558058e-10
 crypto/cipher-stream.js api="legacy" len=2 type="buf" cipher="AES256" writes=500           73.21 %        *** 2.843345e-09
 crypto/cipher-stream.js api="legacy" len=2 type="utf" cipher="AES192" writes=500           62.81 %        *** 5.459900e-06
 crypto/cipher-stream.js api="legacy" len=2 type="utf" cipher="AES256" writes=500           78.31 %        *** 1.773195e-09
 crypto/cipher-stream.js api="stream" len=1024 type="asc" cipher="AES192" writes=500        -6.58 %            3.149545e-01
 crypto/cipher-stream.js api="stream" len=1024 type="asc" cipher="AES256" writes=500       -17.75 %         ** 1.916759e-03
 crypto/cipher-stream.js api="stream" len=1024 type="buf" cipher="AES192" writes=500       -12.84 %          * 2.447441e-02
 crypto/cipher-stream.js api="stream" len=1024 type="buf" cipher="AES256" writes=500       -13.68 %          * 1.926703e-02
 crypto/cipher-stream.js api="stream" len=1024 type="utf" cipher="AES192" writes=500       -13.60 %          * 4.328982e-02
 crypto/cipher-stream.js api="stream" len=1024 type="utf" cipher="AES256" writes=500       -10.14 %            8.210744e-02
 crypto/cipher-stream.js api="stream" len=102400 type="asc" cipher="AES192" writes=500       9.94 %            7.640066e-02
 crypto/cipher-stream.js api="stream" len=102400 type="asc" cipher="AES256" writes=500       7.03 %            1.226431e-01
 crypto/cipher-stream.js api="stream" len=102400 type="buf" cipher="AES192" writes=500       0.63 %            8.830857e-01
 crypto/cipher-stream.js api="stream" len=102400 type="buf" cipher="AES256" writes=500       4.64 %            2.145483e-01
 crypto/cipher-stream.js api="stream" len=102400 type="utf" cipher="AES192" writes=500      -8.94 %            5.658559e-02
 crypto/cipher-stream.js api="stream" len=102400 type="utf" cipher="AES256" writes=500       0.18 %            9.704160e-01
 crypto/cipher-stream.js api="stream" len=1048576 type="asc" cipher="AES192" writes=500     -1.27 %            7.391318e-01
 crypto/cipher-stream.js api="stream" len=1048576 type="asc" cipher="AES256" writes=500      4.94 %            1.405564e-01
 crypto/cipher-stream.js api="stream" len=1048576 type="buf" cipher="AES192" writes=500      8.65 %          * 3.031133e-02
 crypto/cipher-stream.js api="stream" len=1048576 type="buf" cipher="AES256" writes=500      2.01 %            6.064068e-01
 crypto/cipher-stream.js api="stream" len=1048576 type="utf" cipher="AES192" writes=500     -1.37 %            6.641435e-01
 crypto/cipher-stream.js api="stream" len=1048576 type="utf" cipher="AES256" writes=500      1.28 %            6.908288e-01
 crypto/cipher-stream.js api="stream" len=2 type="asc" cipher="AES192" writes=500            9.51 %            1.963920e-01
 crypto/cipher-stream.js api="stream" len=2 type="asc" cipher="AES256" writes=500           -0.15 %            9.812697e-01
 crypto/cipher-stream.js api="stream" len=2 type="buf" cipher="AES192" writes=500           -5.89 %            3.802418e-01
 crypto/cipher-stream.js api="stream" len=2 type="buf" cipher="AES256" writes=500           -4.92 %            4.086042e-01
 crypto/cipher-stream.js api="stream" len=2 type="utf" cipher="AES192" writes=500           -0.17 %            9.805384e-01
 crypto/cipher-stream.js api="stream" len=2 type="utf" cipher="AES256" writes=500            5.64 %            4.003158e-01

@mscdex

mscdex commented Feb 18, 2017 •

Copy link
Copy Markdown
Contributor

The stream regressions are a bit concerning. Have you had a chance to find out why it slows down (so much) in those particular cases?

@jasnell

jasnell commented Feb 18, 2017

Copy link
Copy Markdown
Member Author

That result appears to be rather inconsistent. I'll be running a few more benchmarks on a couple of different platforms to see if those results hold up.

Refactor the crypto module to make the code more readable and
maintainable. Leverages ES6 classes. No test changes are
required. Existing code should keep working.

Provide improved inspect support for crypto classes.

Remove dead code in ECDH.prototype.getPublicKey with an unused
arg type check.
@jasnell
jasnell force-pushed the crypto-refactor-cleanup branch from 629ea29 to 846fe06 Compare February 18, 2017 15:16
assert.throws(() => {
crypto.createECDH();
}, /^TypeError: "curve" argument should be a string$/);
}, /^TypeError: ECDH curve name must be a string$/);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this semver-major? It looked like an internal organization that made no API changes, except for this error message change. Can the error message be made backwards compatible, so we can backport everything else, and just do a semver-major PR afterwards to update the error message for 8.x?

@sam-github

Copy link
Copy Markdown
Contributor

Are there reasons for this to be semver-major, other than the error message change?

@jasnell

jasnell commented Feb 20, 2017 via email

Copy link
Copy Markdown
Member Author

@jasnell jasnell added the wip Issues and PRs that are still a work in progress. label Apr 4, 2017
@jasnell

jasnell commented Apr 4, 2017

Copy link
Copy Markdown
Member Author

Marking this as in progress. It needs a rebase and I need to figure out the perf regression in the stream api fstill

@BridgeAR

Copy link
Copy Markdown
Member

@jasnell it might be interesting to look into this again and to rerun the benchmarks again due to the changes in v8.

@jasnell

jasnell commented Aug 26, 2017

Copy link
Copy Markdown
Member Author

I plan to! It'll be a few weeks tho

@jasnell

jasnell commented Sep 6, 2017

Copy link
Copy Markdown
Member Author

Closing this. Opening a new, updated PR with a slightly different take.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Issues and PRs related to Node.js builds or CI infrastructure. crypto Issues and PRs related to the crypto subsystem. semver-major PRs that contain breaking changes and should be released in the next major version. wip Issues and PRs that are still a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants