| // 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; |
| } |
| } |