blob: d8cfaf8ec339f62b816f8f6d2532c1a0353630ec [file] [log] [blame] [edit]
// See file LICENSE for more information.
library impl.asymmetric_block_cipher.oeap;
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/digests/sha1.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/digests/sha512.dart';
import 'package:pointycastle/random/fortuna_random.dart';
import 'package:pointycastle/src/impl/base_asymmetric_block_cipher.dart';
import 'package:pointycastle/src/platform_check/platform_check.dart';
import 'package:pointycastle/src/registry/registry.dart';
typedef DigestFactory = Digest Function();
/// RSAES-OAEP v2.0
///
/// This implementation is based on the RSAES-OAEP (RSA Encryption Scheme -
/// Optimal Asymmetric Encryption Padding) as specified in section 7.1 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-7.1)
/// _PKCS #1: RSA Cryptography Specifications Version 2.0_.
///
/// **Important:** this is **not** compatible with RSAES-OAEP v2.1 or later (as
/// specified in RFC 3447, RFC 8017, etc.) Those newer versions have an extra
/// 0x00 byte at the beginning of the encoded message (EM) that is passed
/// to the RSA encryption primitive. Therefore, this implementation is
/// incompatible with it, since this is an implementation of v2.0 which does
/// not have that 0x00 byte. A breaking change in the standard!
///
/// Currently, this implementation has the following restrictions:
///
/// - the hash function is hard-coded to be SHA-1 or SHA-256;
/// - the mask generation function is hard-coded to MGF1; and
/// - it cannot accept any _encoding parameters_ (that is, _P_ is always empty)
class OAEPEncoding extends BaseAsymmetricBlockCipher {
/// Intended for internal use.
static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix(
AsymmetricBlockCipher,
'/OAEP',
(_, final Match match) => () {
var underlyingCipher = AsymmetricBlockCipher(match.group(1)!);
return OAEPEncoding(underlyingCipher);
});
/// Hash function used by the EME-OAEP (Encoding Method for Encryption OAEP).
Digest hash;
/// Hash function used by the MGF1 Mask Generation Function.
late Digest mgf1Hash;
/// Default hash of the encoding parameters,
/// all zero octets
Uint8List defHash;
/// The encoding params, or P, as specified in
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-7.1.1)
Uint8List? encodingParams;
final AsymmetricBlockCipher _engine;
late SecureRandom _random;
late bool _forEncryption;
OAEPEncoding._(DigestFactory digestFactory, this._engine,
[this.encodingParams])
: hash = digestFactory(),
defHash = Uint8List(digestFactory().digestSize) {
digestFactory().doFinal(defHash, 0);
}
factory OAEPEncoding(AsymmetricBlockCipher engine,
[Uint8List? encodingParams]) =>
OAEPEncoding.withSHA1(engine, encodingParams);
factory OAEPEncoding.withSHA1(AsymmetricBlockCipher engine,
[Uint8List? encodingParams]) =>
OAEPEncoding._(() => SHA1Digest(), engine, encodingParams);
factory OAEPEncoding.withSHA256(AsymmetricBlockCipher engine,
[Uint8List? encodingParams]) =>
OAEPEncoding._(() => SHA256Digest(), engine, encodingParams);
factory OAEPEncoding.withSHA512(AsymmetricBlockCipher engine,
[Uint8List? encodingParams]) =>
OAEPEncoding._(() => SHA512Digest(), engine, encodingParams);
factory OAEPEncoding.withCustomDigest(
DigestFactory digestFactory, AsymmetricBlockCipher engine,
[Uint8List? encodingParams]) =>
OAEPEncoding._(digestFactory, engine, encodingParams);
@override
String get algorithmName => '${_engine.algorithmName}/OAEP';
@override
void reset() {}
Uint8List _seed() {
var seed = Platform.instance.platformEntropySource().getBytes(32);
return seed;
}
// for compat cleaner translation from java source
Uint8List _arraycopy(
Uint8List src, int srcPos, Uint8List dest, int destPos, int length) {
dest.setRange(
destPos, destPos + length, src.sublist(srcPos, srcPos + length));
return dest;
}
@override
void init(bool forEncryption, CipherParameters params) {
AsymmetricKeyParameter akparams;
mgf1Hash = hash;
if (params is ParametersWithRandom) {
var paramswr = params;
_random = paramswr.random;
akparams = paramswr.parameters as AsymmetricKeyParameter<AsymmetricKey>;
} else {
_random = FortunaRandom();
_random.seed(KeyParameter(_seed()));
akparams = params as AsymmetricKeyParameter<AsymmetricKey>;
}
_engine.init(forEncryption, akparams);
_forEncryption = forEncryption;
// Check type of key provided is suitable
// Note: the _engine can't do this check, because the engine could be used
// for both encryption/decryption and signature/verification (which reverses
// the keys), so its `init` method accepts both types of keys. For example,
// [RSAEngine.init].
if (forEncryption) {
if (akparams.key is! PublicKey) {
throw ArgumentError.value(
'OAEP encryption needs PublicKey: not ${akparams.key.runtimeType}');
}
} else {
if (akparams.key is! PrivateKey) {
throw ArgumentError.value(
'OAEP decryption needs PrivateKey: not ${akparams.key.runtimeType}');
}
}
}
@override
int get inputBlockSize {
var baseBlockSize = _engine.inputBlockSize;
if (_forEncryption) {
return baseBlockSize - 1 - 2 * defHash.length;
} else {
return baseBlockSize;
}
}
@override
int get outputBlockSize {
var baseBlockSize = _engine.outputBlockSize;
if (_forEncryption) {
return baseBlockSize;
} else {
return baseBlockSize - 1 - (2 * defHash.length);
}
}
@override
int processBlock(
Uint8List inp, int inpOff, int len, Uint8List out, int outOff) {
if (_forEncryption) {
return _encodeBlock(inp, inpOff, len, out, outOff);
} else {
return _decodeBlock(inp, inpOff, len, out, outOff);
}
}
/// RSAES-OAEP encryption operation
///
/// Implements the _RSA Encryption Scheme with Optimal Asymmetric Encryption
/// Padding_ (RSAES-OAEP) **encryption operation**. This encryption operation
/// combines the _Encoding Method for Encryption OAEP_ (EME-OAEP)
/// **encoding operation** with the _RSA Encryption Primitive_ (RSAEP).
///
/// This method performs the EME-OAEP encoding operation, and then invokes its
/// [AsymmetricBlockCipher] engine to perform RSAEP to encrypt it.
///
/// The RSAES-OAEP encryption operation is specified in section 7.1.1 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-7.1.1) and the
/// EME-OAEP encoding operation it uses is specified in section 9.1.1.1 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-9.1.1.1).
///
/// The message to be encoded and encrypted is the octet string consisting of
/// [inpLen] bytes from [inp], starting at the [inpOff] offset.
///
/// It returns the ciphertext.
int _encodeBlock(
Uint8List inp, int inpOff, int inpLen, Uint8List out, int outOff) {
if (inpLen > inputBlockSize) {
throw ArgumentError('message too long');
}
// The numbered steps below correspond to the steps in RFC 2437.
// Names in _italics_ refers to names in the RFC 2437 and names in square
// brackets refers to variables in this code.
// 3. Generate PS (padding string containing just zero octets)
//
// In this implementation, the length of PS is always zero. That is, there
// is no bytes in _PS_.
// 4. Calculate _pHash_ = Hash(P)
//
// The result _pHash_ is stored into [pHash].
//
// Note: If no encodingParams are set
// the [defHash] is used as is (which was initialized to be a hash of no
// bytes)
var pHash =
encodingParams != null ? hash.process(encodingParams!) : defHash;
// 5. Calculate _DB_ = pHash || PS || 01 || M
//
// It is the concatenation of _pHash_, _PS_, 0x01 and the message.
// Note: RFC 2437 also includes 'other padding', but that is an error that
// does not appear in subsequent versions of PKCS #1 (e.g. RFC 3447).
//
// The result _DB_ is stored into [block] starting at offset _hLen_ to the
// end.
var block = Uint8List(inputBlockSize + 1 + 2 * pHash.length);
// M: copy the message into the end of the block.
//
// block.setRange(inpOff, block.length - inpLen, inp.sublist(inpLen));
block = _arraycopy(inp, inpOff, block, block.length - inpLen, inpLen);
// 01: add the sentinel byte
//
block[block.length - inpLen - 1] = 0x01;
// PS: since a Uint8List is initialized with 0x00, PS is already zeroed
// pHash: add the hash of the encoding params.
//
block = _arraycopy(pHash, 0, block, pHash.length, pHash.length);
// 6. Generate a random octet string _seed_ of length _hLen_.
//
// The _seed_ is stored in [seed].
var seed = _random.nextBytes(pHash.length);
// 7. Calculate _dbMask_ = MGF(seed, emLen - hLen)
//
// The _seed_ comes from [seed]. The result _dbMask_ is stored into [mask].
var mask = _maskGeneratorFunction1(
seed, 0, seed.length, block.length - pHash.length);
// 8. Calculate _maskedDB_ = DB XOR dbMask
//
// The _DB_ comes from [block], starting at offset _hLen_ to the end. The
// _dbMask_ comes from [mask]. The result _maskedDB_ is stored into [block]
// starting at offset _hLen_ to the end (overwriting the _DB_).
for (var i = pHash.length; i != block.length; i++) {
block[i] ^= mask[i - pHash.length];
}
// Temporally store the _seed_ in the first _hLen_ bytes of the [block]
// so it can be used later.
block = _arraycopy(seed, 0, block, 0, pHash.length);
// 9. Calculate _seedMask_ = MGF(maskedDB, hLen)
//
// The _maskedDB_ comes from [block], starting at offset _hLen_ to the end.
// The result _seedMask_ is stored into [mask] (replacing the _dbMask_ which
// is no longer needed).
mask = _maskGeneratorFunction1(
block, pHash.length, block.length - pHash.length, pHash.length);
// 10. Calculate _maskedSeed_ = seed XOR seedMask
//
// The _seed_ comes from [block], the first _hLen_ bytes (where it was
// temporally stored). The _seedMask_ comes from [mask]. The result
// _maskedSeed_ is stored into [block], the first _hLen_ bytes (overwriting
// the temporary _seed_).
for (var i = 0; i != pHash.length; i++) {
block[i] ^= mask[i];
}
// 11. Calculate _EM_ = maskedSeed || maskedDB
//
// The [block] already contains the concatenated value, since they were both
// calculated in the first.
// EME-OAEP-ENCODE completed.
// Use the [_engine] to finish the RSAES-OAEP. That is, it will convert the
// _EM_ into an integer, apply the RSA Encryption Primitive (RSAEP) to the
// public key, and convert the resulting integer ciphertext representation
// into octets. The octets will be written into [out] starting at [outOff].
//
// Returns the number of bytes in the output ciphertext.
return _engine.processBlock(block, 0, block.length, out, outOff);
}
/// RSAES-OAEP decryption operation
///
/// Implements the _RSA Encryption Scheme with Optimal Asymmetric Encryption
/// Padding_ (RSAES-OAEP) **decryption operation**. This decryption operation
/// combines the _RSA Decryption Primitive_ (RSADP) with the _Encoding Method
/// for Encryption OAEP_ (EME-OAEP) **decoding operation**.
///
/// This method invokes its [AsymmetricBlockCipher] engine to perform RSADP,
/// and then performs the EME-OAEP decoding operation on the decrypted data.
///
/// The RSAES-OAEP decryption operation is specified in section 7.1.2 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-7.1.2) and the
/// EME-OAEP decoding operation it uses is specified in section 9.1.1.2 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-9.1.1.2).
///
/// The ciphertext to be decrypted and decoded is the octet string consisting
/// of [inpLen] bytes from [inp], starting at the [inpOff] offset.
///
/// It returns the message in [out] starting at offset [outOff].
int _decodeBlock(
Uint8List inp, int inpOff, int inpLen, Uint8List out, int outOff) {
// The numbered steps below correspond to the steps from section 7.1.2 of
// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-7.1.2).
//
// Names in _italics_ refers to names in the RFC 2437 and names in square
// brackets refers to variables in this code.
// 1. Length checking
if (inpLen != _engine.inputBlockSize) {
throw ArgumentError.value(inpLen, 'inpLen', 'decryption error');
}
// 2, 3. RSA decryption
var block = Uint8List(_engine.outputBlockSize);
var decryptFailed = false;
try {
var len = _engine.processBlock(inp, inpOff, inpLen, block, 0);
// 4. EM = I2OSP(m, k-1)
if (len < block.length) {
// Decrypted bytes is shorter than expected. Add 0x00 bytes at the
// beginning of the block (i.e. ensure it is k-1 long). This is needed
// when there were 0x00 in the leading bytes of the block that was
// originally encrypted.
// Note: do not use [_arrayCopy] or [SetRange], since the source and
// destination may overlap. Those methods will corrupt the data.
// Copy [len] data bytes from beginning of block to its end. I.e. from
// block[block.length - 1] <- block[len - 1] through to
// block[block.len - len] <- block[0]
for (var x = 0; x < len; x++) {
block[block.length - 1 - x] = block[len - 1 - x];
}
// Put 0x00 in those beginning bytes. Important: do this AFTER copying
block.fillRange(0, block.length - len, 0x00);
}
} on ArgumentError {
decryptFailed = true;
}
// 5. EME-OAEP decoding
//
// In these 5.x numbered steps, the x refers to steps from section 9.1.1.2
// of [RFC 2437](https://tools.ietf.org/html/rfc2437#section-9.1.1.2)
// 5.2 Check length
var wrongData = block.length < (2 * defHash.length) + 1;
// 5.4 Calculate _seedMask_ = MGF(maskedDB, hLen)
//
// The _maskedDB_ comes from [block] starting at _hLen_ to the end.
// The result _seedMask_ is stored in [mask].
var mask = _maskGeneratorFunction1(
block, defHash.length, block.length - defHash.length, defHash.length);
// 5.5 Calculate _seed_ = maskedSeed XOR seedMask
//
// THe _maskedSeed_ comes from the first _hLen_ bytes of [block] and the
// _seedMask_ comes from [mask].
// The result _seed_ is stored in the first _hLen_ bytes of [block]
// (overwriting the maskedSeed_ that was previously there).
for (var i = 0; i != defHash.length; i++) {
block[i] ^= mask[i];
}
// 5.6 Calculate _dbMask_ = MGF(seed, length of EM - hLen)
mask = _maskGeneratorFunction1(
block, 0, defHash.length, block.length - defHash.length);
// 5.7 Calculate _DB_ = maskedDB XOR dbMask
//
// The _maskedDB_ comes from [block], from _hLen_ to the end, and the
// _dbMask_ comes from [mask]. The result _DB_ is stored in [block] from
// _hLen_ to the end (overwriting the _maskedDB_ that was previously there).
for (var i = defHash.length; i != block.length; i++) {
block[i] ^= mask[i - defHash.length];
}
// 5.8 pHash = Hash(P)
var pHash =
encodingParams != null ? hash.process(encodingParams!) : defHash;
// 5.10 Check _pHash'_ to _pHash_
//
// check the hash of the encoding params.
// long check to try to avoid this been a source of a timing attack.
//
// The _pHash'_ comes from the first _hLen_ bytes of [block]
var pHashWrong = false;
for (var i = 0; i != pHash.length; i++) {
pHashWrong |= pHash[i] != block[pHash.length + i];
}
// 5.9 Split _DB_ into pHash1 || PS || 0x01 || M
//
// Skip over the _PS_ (which are all 0x00 bytes). Finding the first non-zero
// byte from hash.digestLength * 2 to the end of [block]. Setting [start]
// to that first non-zero byte (or will be block.length if none found).
var start = block.length;
for (var index = 2 * pHash.length; index != block.length; index++) {
if ((block[index] != 0) & (start == block.length)) {
start = index;
}
}
// The data-start-is-wrong if the rest of the [block] contains all 0x00
// bytes or that first non-zero byte is not 0x01.
var dataStartWrong = (start > (block.length - 1)) |
(start < block.length && block[start] != 0x01);
start++;
if (decryptFailed || pHashWrong || wrongData || dataStartWrong) {
block.fillRange(0, block.length, 0);
throw ArgumentError('decoding error');
}
// 5.11 Output M
//
// The _M_ are all the bytes from after the 0x01 byte (i.e. offset [start])
// to the end of [block]. Copy those bytes into [out] starting at [outOff].
final mLen = block.length - start;
_arraycopy(block, start, out, outOff, mLen);
return mLen;
}
// ignore: slash_for_doc_comments
/// int to octet string.
Uint8List _itoOSP(int i, Uint8List sp) {
sp[0] = i >> 24;
sp[1] = i >> 16;
sp[2] = i >> 8;
sp[3] = i >> 0;
return sp;
}
/// Implementation of MGF1 (the Mask Generation Function from PKCS #1 v2.0).
///
/// See section 10.2.1 of
/// [RFC 2437](https://tools.ietf.org/html/rfc2437#section-10.2.1).
///
/// MGF1 is defined to take a hash function as an option. This implementation
/// uses [mgf1Hash] for that hash function.
///
/// MGF1 hsa two inputs: a seed and an intended length. The seed is the
/// sequence of bytes in [Z], starting at [zOff] for [zLen] bytes.
/// The intended length is in [length].
///
/// Returns the calculated mask. A Uint8List that contains [length] bytes.
Uint8List _maskGeneratorFunction1(
Uint8List Z, int zOff, int zLen, int length) {
var mask = Uint8List(length);
var hashBuf = Uint8List(mgf1Hash.digestSize);
var C = Uint8List(4);
var counter = 0;
mgf1Hash.reset();
while (counter < (length / hashBuf.length).floor()) {
_itoOSP(counter, C);
mgf1Hash.update(Z, zOff, zLen);
mgf1Hash.update(C, 0, C.length);
mgf1Hash.doFinal(hashBuf, 0);
mask = _arraycopy(
hashBuf, 0, mask, counter * hashBuf.length, hashBuf.length);
counter++;
}
if ((counter * hashBuf.length) < length) {
_itoOSP(counter, C);
mgf1Hash.update(Z, zOff, zLen);
mgf1Hash.update(C, 0, C.length);
mgf1Hash.doFinal(hashBuf, 0);
mask = _arraycopy(hashBuf, 0, mask, counter * hashBuf.length,
mask.length - (counter * hashBuf.length));
}
return mask;
}
}