diff --git a/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll b/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll index 22a2d1c1eb29..3a930f721cf2 100644 --- a/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll +++ b/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll @@ -9,7 +9,7 @@ private import internal.CryptoAlgorithmNames /** * A cryptographic algorithm. */ -private newtype TCryptographicAlgorithm = +private newtype TCryptographicAlgorithm = MkHashingAlgorithm(string name, boolean isWeak) { isStrongHashingAlgorithm(name) and isWeak = false or @@ -25,6 +25,9 @@ private newtype TCryptographicAlgorithm = or isWeakPasswordHashingAlgorithm(name) and isWeak = true } + or + MkUnknown() + /** * A cryptographic algorithm. @@ -53,6 +56,26 @@ abstract class CryptographicAlgorithm extends TCryptographicAlgorithm { * Holds if this algorithm is weak. */ abstract predicate isWeak(); + + /** + * Holds if this algorithm is not known. + */ + predicate isUnknown() { this instanceof UnknownAlgorithm } +} + +/** + * An 'Unknown' cryptographic algorithm, typically encountered when extracting as yet unmodelled API algorithms. + */ +class UnknownAlgorithm extends MkUnknown, CryptographicAlgorithm { + override predicate isUnknown() { any() } + + override string getName() { result = unknownAlgorithm() } + + override predicate isWeak() { any() } + + bindingset[name] + override predicate matchesName(string name) { none()} + } /** @@ -82,6 +105,8 @@ class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm override predicate isWeak() { isWeak = true } + predicate isAsymmetricEncryption() { isAsymmetricEncryption(name) } + /** Holds if this algorithm is a stream cipher. */ predicate isStreamCipher() { isStreamCipher(name) } } diff --git a/python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll b/python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll index a234ba2cc1fc..8d96277e9f7f 100644 --- a/python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll +++ b/python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll @@ -8,14 +8,34 @@ * The classification into strong and weak are based on Wikipedia, OWASP and Google (2021). */ + /** + * Returns a string to represent generally unknown algorithms. + * Predicate is to be used to get a consistent string representation + * for unknown algorithms. + */ + string unknownAlgorithm() + { + result = "UNKNOWN" + } + + + /** + * Holds if `name` is a known hashing algorithm in the model/library. + */ + predicate isKnownHashingAlgorithm(string name) + { + isStrongHashingAlgorithm(name) or isWeakHashingAlgorithm(name) + } + /** * Holds if `name` corresponds to a strong hashing algorithm. */ predicate isStrongHashingAlgorithm(string name) { name = [ - "DSA", "ED25519", "ES256", "ECDSA256", "ES384", "ECDSA384", "ES512", "ECDSA512", "SHA2", - "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "SHA3224", "SHA3256", "SHA3384", "SHA3512" + "BLAKE2", "BLAKE2b", "BLAKE2s", "DSA", "ED25519", "ES256", "ECDSA256", "ES384", "ECDSA384", "ES512", "ECDSA512", "SHA2", + "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "SHA3224", "SHA3256", "SHA3384", "SHA3512", + "SHAKE128", "SHAKE256" ] } @@ -30,16 +50,25 @@ predicate isWeakHashingAlgorithm(string name) { ] } + /** + * Holds if `name` is a known encryption algorithm in the model/library. + */ + predicate isKnownEncryptionAlgorithm(string name) + { + isStrongEncryptionAlgorithm(name) or isWeakEncryptionAlgorithm(name) + } + + /** * Holds if `name` corresponds to a strong encryption algorithm. */ predicate isStrongEncryptionAlgorithm(string name) { name = [ - "AES", "AES128", "AES192", "AES256", "AES512", "AES-128", "AES-192", "AES-256", "AES-512", + "AES", "AES128", "AES192", "AES256", "AES512", "ARIA", "BLOWFISH", "BF", "ECIES", "CAST", "CAST5", "CAMELLIA", "CAMELLIA128", "CAMELLIA192", - "CAMELLIA256", "CAMELLIA-128", "CAMELLIA-192", "CAMELLIA-256", "CHACHA", "GOST", "GOST89", - "IDEA", "RABBIT", "RSA", "SEED", "SM4" + "CAMELLIA256", "CHACHA", "GOST", "GOST89", + "IDEA", "RABBIT", "RSA", "SEED", "SM3", "SM4" ] } @@ -54,6 +83,15 @@ predicate isWeakEncryptionAlgorithm(string name) { ] } + /** + * Holds if `name` is a known password hashing algorithm in the model/library. + */ + predicate isKnownPasswordHashingAlgorithm(string name) + { + isStrongPasswordHashingAlgorithm(name) or isWeakPasswordHashingAlgorithm(name) + } + + /** * Holds if `name` corresponds to a strong password hashing algorithm. */ @@ -64,9 +102,46 @@ predicate isStrongPasswordHashingAlgorithm(string name) { /** * Holds if `name` corresponds to a weak password hashing algorithm. */ -predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" } +predicate isWeakPasswordHashingAlgorithm(string name) { + name = "EVPKDF" +} + + /** + * Holds if `name` is a known cipher block mode algorithm in the model/library. + */ + predicate isKnownCipherBlockModeAlgorithm(string name) + { + isStrongCipherBlockModeAlgorithm(name) or isWeakCipherBlockModeAlgorithm(name) + } + + +/** + * Holds if `name` corresponds to a strong cipher block mode + */ +predicate isStrongCipherBlockModeAlgorithm(string name) +{ + name = ["CBC", "GCM", "CCM", "CFB", "OFB", "CFB8", "CTR", "OPENPGP", "XTS", "EAX"] +} + +/** + * Holds if `name` corresponds to a weak cipher block mode + */ +predicate isWeakCipherBlockModeAlgorithm(string name) +{ + name = ["ECB"] +} + /** * Holds if `name` corresponds to a stream cipher. */ predicate isStreamCipher(string name) { name = ["CHACHA", "RC4", "ARC4", "ARCFOUR", "RABBIT"] } + + +/** + * Holds if `name` corresponds to an asymmetric encryption. + */ +bindingset[name] +predicate isAsymmetricEncryption(string name){ + name.regexpMatch("(?i)^rsa.*") +} diff --git a/python/ql/lib/semmle/python/frameworks/Cryptodome.qll b/python/ql/lib/semmle/python/frameworks/Cryptodome.qll index 0c3e8968c23b..b7be62e9e59d 100644 --- a/python/ql/lib/semmle/python/frameworks/Cryptodome.qll +++ b/python/ql/lib/semmle/python/frameworks/Cryptodome.qll @@ -9,6 +9,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.ApiGraphs +import semmle.python.concepts.internal.CryptoAlgorithmNames /** * Provides models for @@ -124,7 +125,9 @@ private module CryptodomeModel { this = newCall.getReturn().getMember(methodName).getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(cipherName) } + override string getAlgorithmRaw() { + result = cipherName + } override DataFlow::Node getAnInput() { methodName = "encrypt" and @@ -156,18 +159,23 @@ private module CryptodomeModel { ] } - override Cryptography::BlockMode getBlockMode() { - // `modeName` is of the form "MODE_" - exists(string modeName | - newCall.getArg(1) = - API::moduleImport(["Crypto", "Cryptodome"]) - .getMember("Cipher") - .getMember(cipherName) - .getMember(modeName) - .getAValueReachableFromSource() - | - result = modeName.splitAt("_", 1) - ) + private predicate resolveModeName(string modeName) + { + newCall.getArg(1) = + API::moduleImport(["Crypto", "Cryptodome"]) + .getMember("Cipher") + .getMember(cipherName) + .getMember(modeName) + .getAValueReachableFromSource() + } + + override Cryptography::BlockMode getBlockModeRaw() { + // `modeName` is of the form "MODE_" + exists(string modeName | + if resolveModeName(modeName) + then result = modeName.splitAt("_", 1) + else modeName = unknownAlgorithm() and + result = unknownAlgorithm()) } } @@ -191,8 +199,8 @@ private module CryptodomeModel { .getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { - result.matchesName(signatureName) + override string getAlgorithmRaw() { + result = signatureName } override DataFlow::Node getAnInput() { @@ -207,7 +215,7 @@ private module CryptodomeModel { ) } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -228,10 +236,12 @@ private module CryptodomeModel { ) } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) } + override string getAlgorithmRaw() { + result = hashName + } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } } diff --git a/python/ql/lib/semmle/python/frameworks/Cryptography.qll b/python/ql/lib/semmle/python/frameworks/Cryptography.qll index d3e03083c09d..0a6b3fb358e1 100644 --- a/python/ql/lib/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/lib/semmle/python/frameworks/Cryptography.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.ApiGraphs +import semmle.python.concepts.internal.CryptoAlgorithmNames /** * Provides models for the `cryptography` PyPI package. @@ -180,7 +181,7 @@ private module CryptographyModel { } /** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */ - API::Node cipherInstance(string algorithmName, string modeName) { + API::Node symmetricCipherInstance(string algorithmName, string modeName) { exists(API::CallNode call | result = call.getReturn() | call = API::moduleImport("cryptography") @@ -193,10 +194,12 @@ private module CryptographyModel { call.getArg(0), call.getArgByName("algorithm") ] and exists(DataFlow::Node modeArg | modeArg in [call.getArg(1), call.getArgByName("mode")] | + // Find the used mode name if modeArg = modeClassRef(_).getReturn().getAValueReachableFromSource() then modeArg = modeClassRef(modeName).getReturn().getAValueReachableFromSource() - else modeName = "" + else modeName = unknownAlgorithm() ) + ) } @@ -210,20 +213,20 @@ private module CryptographyModel { CryptographyGenericCipherOperation() { this = - cipherInstance(algorithmName, modeName) + symmetricCipherInstance(algorithmName, modeName) .getMember(["decryptor", "encryptor"]) .getReturn() .getMember(["update", "update_into"]) .getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { - result.matchesName(algorithmName) + override string getAlgorithmRaw() { + result = algorithmName } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] } - override Cryptography::BlockMode getBlockMode() { result = modeName } + override string getBlockModeRaw() { result = modeName } } } @@ -269,13 +272,13 @@ private module CryptographyModel { this = hashInstance(algorithmName).getMember("update").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { - result.matchesName(algorithmName) + override string getAlgorithmRaw() { + result = algorithmName } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } } } diff --git a/python/ql/lib/semmle/python/frameworks/Rsa.qll b/python/ql/lib/semmle/python/frameworks/Rsa.qll index e82581d46b65..3a1a99b6e411 100644 --- a/python/ql/lib/semmle/python/frameworks/Rsa.qll +++ b/python/ql/lib/semmle/python/frameworks/Rsa.qll @@ -36,13 +36,15 @@ private module Rsa { class RsaEncryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { RsaEncryptCall() { this = API::moduleImport("rsa").getMember("encrypt").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + override string getAlgorithmRaw() { + result = "RSA" + } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("message")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -53,11 +55,13 @@ private module Rsa { class RsaDecryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { RsaDecryptCall() { this = API::moduleImport("rsa").getMember("decrypt").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + override string getAlgorithmRaw() { + result = "RSA" + } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("crypto")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -68,23 +72,22 @@ private module Rsa { class RsaSignCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { RsaSignCall() { this = API::moduleImport("rsa").getMember("sign").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { + override string getAlgorithmRaw() { // signature part - result.getName() = "RSA" + result = "RSA" or // hashing part exists(StrConst str, DataFlow::Node hashNameArg | hashNameArg in [this.getArg(2), this.getArgByName("hash_method")] and DataFlow::exprNode(str) = hashNameArg.getALocalSource() and - result.matchesName(str.getText()) - ) + result = str.getText()) } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("message")] } - override Cryptography::BlockMode getBlockMode() { none() } + override Cryptography::BlockMode getBlockModeRaw() { none() } } /** @@ -95,10 +98,10 @@ private module Rsa { class RsaVerifyCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { RsaVerifyCall() { this = API::moduleImport("rsa").getMember("verify").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { + override string getAlgorithmRaw() { // note that technically there is also a hashing operation going on but we don't // know what algorithm is used up front, since it is encoded in the signature - result.getName() = "RSA" + result = "RSA" } override DataFlow::Node getAnInput() { @@ -107,7 +110,7 @@ private module Rsa { result in [this.getArg(1), this.getArgByName("signature")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -119,19 +122,18 @@ private module Rsa { DataFlow::CallCfgNode { RsaComputeHashCall() { this = API::moduleImport("rsa").getMember("compute_hash").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { + override string getAlgorithmRaw() { exists(StrConst str, DataFlow::Node hashNameArg | hashNameArg in [this.getArg(1), this.getArgByName("method_name")] and DataFlow::exprNode(str) = hashNameArg.getALocalSource() and - result.matchesName(str.getText()) - ) + result = str.getText()) } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("message")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -142,12 +144,14 @@ private module Rsa { class RsaSignHashCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { RsaSignHashCall() { this = API::moduleImport("rsa").getMember("sign_hash").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + override string getAlgorithmRaw() { + result = "RSA" + } override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("hash_value")] } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index f5d6dd8df1cf..1ef6e874f1e4 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -2651,6 +2651,32 @@ private module StdlibPrivate { // --------------------------------------------------------------------------- // hashlib // --------------------------------------------------------------------------- + + + private API::CallNode hashlibpbkdf2HMACCall(string algorithmName) + { + algorithmName = + result.getParameter(0, "hash_name").getAValueReachingSink().asExpr().(StrConst).getText() and + result = API::moduleImport("hashlib").getMember("pbkdf2_hmac").getACall() + } + + class Hashlibpbkdf2HMAC extends Cryptography::CryptographicOperation::Range, API::CallNode { + string hashName; + + Hashlibpbkdf2HMAC() { + this = hashlibpbkdf2HMACCall(hashName) and + exists(this.getParameter(1, "data")) + } + + override string getAlgorithmRaw() { + result = hashName + } + + override DataFlow::Node getAnInput() { result = this.getParameter(1, "password").asSink() } + + override string getBlockModeRaw() { none() } + } + /** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */ private API::CallNode hashlibNewCall(string algorithmName) { algorithmName = @@ -2669,11 +2695,13 @@ private module StdlibPrivate { exists(this.getParameter(1, "data")) } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) } + override string getAlgorithmRaw() { + result = hashName + } override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").asSink() } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** @@ -2686,24 +2714,27 @@ private module StdlibPrivate { this = hashlibNewCall(hashName).getReturn().getMember("update").getACall() } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) } + override string getAlgorithmRaw() { + result = hashName + } override DataFlow::Node getAnInput() { result = this.getArg(0) } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** Helper predicate for the `HashLibGenericHashOperation` charpred, to prevent a bad join order. */ pragma[nomagic] private API::Node hashlibMember(string hashName) { result = API::moduleImport("hashlib").getMember(hashName) and - hashName != "new" + hashName != "new" and hashName != "pbkdf2_hmac" } /** * A hashing operation from the `hashlib` package using one of the predefined classes * (such as `hashlib.md5`). `hashlib.new` is not included, since it is handled by * `HashlibNewCall` and `HashlibNewUpdateCall`. + * `hashlib.pbkdf2_hmac` is also not included and is handled by `Hashlibpbkdf2HMAC` */ abstract class HashlibGenericHashOperation extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { @@ -2713,9 +2744,11 @@ private module StdlibPrivate { bindingset[this] HashlibGenericHashOperation() { hashClass = hashlibMember(hashName) } - override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) } + override string getAlgorithmRaw() { + result = hashName + } - override Cryptography::BlockMode getBlockMode() { none() } + override string getBlockModeRaw() { none() } } /** diff --git a/python/ql/lib/semmle/python/internal/ConceptsShared.qll b/python/ql/lib/semmle/python/internal/ConceptsShared.qll index 2f6c8bb8b29b..b8c8a2df2a29 100644 --- a/python/ql/lib/semmle/python/internal/ConceptsShared.qll +++ b/python/ql/lib/semmle/python/internal/ConceptsShared.qll @@ -10,8 +10,11 @@ * `ConceptsShared.qll` ASAP. */ +import semmle.python.concepts.internal.CryptoAlgorithmNames +import semmle.python.concepts.CryptoAlgorithms private import ConceptsImports + /** * Provides models for cryptographic concepts. * @@ -21,13 +24,7 @@ private import ConceptsImports * to improve our libraries in the future to more precisely capture this aspect. */ module Cryptography { - class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm; - - class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm; - - class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm; - - class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm; + import semmle.python.concepts.CryptoAlgorithms /** * A data-flow node that is an application of a cryptographic algorithm. For example, @@ -38,17 +35,27 @@ module Cryptography { */ class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range { /** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */ - CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() } + CryptographicAlgorithm getAlgorithm() { + result.matchesName(super.getAlgorithmRaw()) or + not exists(CryptographicAlgorithm algo | algo.matchesName(super.getAlgorithmRaw())) and result instanceof UnknownAlgorithm } + /** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */ DataFlow::Node getAnInput() { result = super.getAnInput() } + /** * Gets the block mode used to perform this cryptographic operation. * This may have no result - for example if the `CryptographicAlgorithm` used * is a stream cipher rather than a block cipher. */ - BlockMode getBlockMode() { result = super.getBlockMode() } + final BlockMode getBlockMode() { + if isKnownCipherBlockModeAlgorithm(super.getBlockModeRaw()) then + result = super.getBlockModeRaw() + else + result = unknownAlgorithm() + } + } /** Provides classes for modeling new applications of a cryptographic algorithms. */ @@ -61,30 +68,45 @@ module Cryptography { * extend `CryptographicOperation` instead. */ abstract class Range extends DataFlow::Node { - /** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */ - abstract CryptographicAlgorithm getAlgorithm(); + /** Gets the raw algorithm used, i.e., the algorithm extracted directly from the source*/ + abstract string getAlgorithmRaw(); + /** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */ abstract DataFlow::Node getAnInput(); - /** - * Gets the block mode used to perform this cryptographic operation. - * This may have no result - for example if the `CryptographicAlgorithm` used - * is a stream cipher rather than a block cipher. - */ - abstract BlockMode getBlockMode(); + /** Gets the raw block mode used, i.e., the block mode extracted directly from the source*/ + abstract string getBlockModeRaw(); } } + /** * A cryptographic block cipher mode of operation. This can be used to encrypt * data of arbitrary length using a block encryption algorithm. */ class BlockMode extends string { - BlockMode() { this = ["ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP"] } + BlockMode() { + isKnownCipherBlockModeAlgorithm(this) or + ((not isKnownCipherBlockModeAlgorithm(this)) and this = unknownAlgorithm()) + } - /** Holds if this block mode is considered to be insecure. */ - predicate isWeak() { this = "ECB" } + /** + * Holds if this block mode is known and considered to be insecure. + * NOTE: if the algorithm is not known, no assessment is made on if it is secure. + * Users should use the `isUnknown` predicate specifically to make their + * own assessment in these conditions. + */ + predicate isWeak() { + isWeakCipherBlockModeAlgorithm(this) + } + + /** + * Holds if this block mode is not a known block mode + */ + predicate isUnknown() { + not isKnownCipherBlockModeAlgorithm(this) + } } }