ethers.js/docs.wrm/concepts/security/index.wrm
2022-02-03 15:46:18 -05:00

163 lines
6.3 KiB
Plaintext

_section: Security @<security>
While security should be a concern for all developers, in the
blockchain space developers must be additionally conscious of
many areas which can be exploited.
Once a problem has an economic incentives to exploit it, there
is a much larger risk and with blockchain apps it can become
quite valuable to attack.
In addition to many of the other security issues app developers
may have to worry about, there are a few additional vectors
that JavaScript developers should be aware of.
_subsection: Side-Channel Attacks
A [Side-Channel Attack](link-wiki-side-channel-attack) occurs
when something orthogonal to the implementation of the algorithm
used can be exploited to learn more about secure or private
information.
_heading: Released Data (Strings, Uint8Arrays, Buffers)
In JavaScript, memory may not be securely allocated, or more
importantly securely released.
[Historically](https://github.com/nodejs/node/issues/4660),
``new Buffer(16)`` would re-use old memory that had been
released. This would mean that code running later, may have
access to data that was discarded.
As an example of the dangers, imagine if you had used a Buffer
to store a private key, signed data and then returned from the
function, allowing the Buffer to be de-allocated. A future
function may be able to request a new Buffer, which would still
have that left-over private key, which it could then use to
steal the funds from that account.
There are also many debugging tools and systems designed to
assist developers inspect the memory contents of JavaScript
programs. In these cases, any //private key// or //mnemonic//
sitting in memory may be visible to other users on the system,
or malicious scripts.
_heading: Timing Attack
Timing attacks allow a malicious user or script to determine
private data through analysing how long an operation requires
to execute.
In JavaScript, //Garbage Collection// occurs periodically when the
system determines it is required. Each JavaScript implementation
is different, with a variety of strategies and and abilities.
Most Garbage Collection requires "stopping the world", or pausing
all code being executed while it runs. This adds a large delay
to any code that was currently running.
This can be exploited by attackers to "condition cause a delay".
They will set up a scenario where the system is on the edge of
needing to garbage collect, and call your code with two paths,
a simple path and complex path. The simple path won't stir things
up enough to cause a garbage collection, while the complex one
will. By timing how long the code took to execute, they now know
whether garbage collection occured and therefore whether the simple
or complex path was taken.
Advanced timing attacks are very difficult to mitigate in any
garbage-collection-based language. Most libraries where this
matters will hopefully mitigate this for you as much as possible,
but it is still good to be aware of.
_heading: General Concerns
- [Cross-Site Scripting](link-wiki-xss)
- [Cross-Site Request Forgery](link-wiki-csrf)
- [Phishing](link-wiki-phishing)
_subsection: Key Derivation Functions @<security--pbkdf>
This is not specific to Ethereum, but is a useful technique
to understand and has some implications on User Experience.
Many people are concerned that encrypting and decrypting an
Ethereum wallet is quite slow and can take quite some time.
It is important to understand this is intentional and provides
much stronger security.
The algorithm usually used for this process is [scrypt](link-wiki-scrypt),
which is a memory and CPU intensive algorithm which computes
a key (fixed-length pseudo-random series of bytes) for a given
password.
_heading: Why does it take so long?
The goal is to use as much CPU and memory as possible during
this algorithm, so that a single computer can only compute a
very small number of results for some fixed amount of time. To
scale up an attack, the attacker requires additional computers,
increasing the cost to [brute-force attack](link-wiki-bruteforce)
to guess the password.
For example, if a user knows their correct password, this process
may take 10 seconds for them to unlock their own wallet and proceed.
But since an attacker does not know the password, they must guess; and
each guess also requires 10 seconds. So, if they wish to try guessing 1
million passwords, their computer would be completely tied up for 10
million seconds, or around 115 days.
Without using an algorithm like this, a user would be able
to log in instantly, however, 1 million passwords would only
take a few seconds to attempt. Even secure passwords would
likely be broken within a short period of time. There is no way
the algorithm can be faster for a legitimate user without also
being faster for an attacker.
_heading: Mitigating the User Experience
Rather than reducing the security (see below), a better practice is to make
the user feel better about waiting. The Ethers encryption and decryption
API allows the developer to incorporate a progress bar, by passing in a
progress callback which will be periodically called with a number between
0 and 1 indication percent completion.
In general a progress bar makes the experience feel faster, as well as
more comfortable since there is a clear indication how much (relative) time
is remaining. Additionally, using language like //"decrypting..."// in
a progress bar makes a user feel like their time is not being //needlessly//
wasted.
_heading: Work-Arounds (not recommended)
There are ways to reduce the time required to decrypt an Ethereum JSON
Wallet, but please keep in mind that doing so **discards nearly all security**
on that wallet.
The scrypt algorithm is designed to be tuned. The main purpose of this is
to increase the difficulty as time goes on and computers get faster, but
it can also be tuned down in situations where the security is less important.
_code: @LANG<javascript>
// Our wallet object
const wallet = Wallet.createRandom();
// The password to encrypt with
const password = "password123";
// WARNING: Doing this substantially reduces the security
// of the wallet. This is highly NOT recommended.
// We override the default scrypt.N value, which is used
// to indicate the difficulty to crack this wallet.
const json = wallet.encrypt(password, {
scrypt: {
// The number must be a power of 2 (default: 131072)
N: 64
}
});