Skip to content

Commit e8ed886

Browse files
mschragMike Schrag
andauthored
Incorrect maximum encryption size for RSA OAEP (#423)
Incorrect maximum encryption size for RSA OAEP ### Checklist - [X] I've run tests to see all new and existing tests pass - [X] I've followed the code style of the rest of the project - [X] I've read the [Contribution Guidelines](CONTRIBUTING.md) - [X] I've updated the documentation if necessary #### If you've made changes to `gyb` files - [n/a] I've run `./scripts/generate_boilerplate_files_with_gyb.sh` and included updated generated files in a commit of this pull request ### Motivation: The `maximumEncryptSize` function is hardcoded to use 42 as the hash offset, but the RFC actually says it's "2*hLen-2" so 42 is only valid for SHA1. SHA256 should be 62 (2*32-2). This adds a hash length onto the Digest enum, which can then be used in the length calculation. In writing the tests for this, I also stumbled on the unsafe PEM representation for RSA Public Keys don't allow 1024-bit keys as documented, so this also fixes that. ### Modifications: * Added a `hashBitLength` to the RSA Digest which is then used in the `maximumEncryptSize` to properly compute the maximum length * Corrected the minimum key size for RSA unsafe PEM public keys to 1024 from 2048 ### Result: * The `maximumEncryptSize` function will return the expected value for RSA OAEP SHA256 keys. I don't believe anything calls this internally, so this would be for external consumers. * Unsafe construction of RSA public keys now allow 1024 bit keys. It was documented as supporting them, but the check was still 2048 (probably copy-paste error from the safe variant). This only expands the potential uses, so it shouldn't introduce any new failures. Co-authored-by: Mike Schrag <[email protected]>
1 parent 87a9e06 commit e8ed886

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

Sources/CryptoExtras/RSA/RSA.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ extension _RSA.Encryption {
508508
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
509509
public init(unsafePEMRepresentation pemRepresentation: String) throws {
510510
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)
511-
guard self.keySizeInBits >= 2048, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize }
511+
guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize }
512512
}
513513

514514
/// Construct an RSA public key from a DER representation.
@@ -675,6 +675,14 @@ extension _RSA.Encryption {
675675
internal enum Digest {
676676
case sha1
677677
case sha256
678+
679+
/// Returns the number of bits in the resulting hash
680+
var hashBitLength: Int {
681+
switch self {
682+
case .sha1: return 160
683+
case .sha256: return 256
684+
}
685+
}
678686
}
679687
}
680688

@@ -694,7 +702,7 @@ extension _RSA.Encryption.PublicKey {
694702
/// Return the maximum amount of data in bytes this key can encrypt in a single operation when using
695703
/// the specified padding mode.
696704
///
697-
/// ## Common values:
705+
/// ## Common values (for PKCS1 OAEP SHA1):
698706
///
699707
/// Key size|Padding|Max length
700708
/// -|-|-
@@ -703,8 +711,9 @@ extension _RSA.Encryption.PublicKey {
703711
/// 4096|PKCS-OAEP|470 bytes
704712
public func maximumEncryptSize(with padding: _RSA.Encryption.Padding) -> Int {
705713
switch padding.backing {
706-
case .pkcs1_oaep:
707-
return (self.keySizeInBits / 8) - 42
714+
case let .pkcs1_oaep(Digest):
715+
// https://datatracker.ietf.org/doc/html/rfc8017#section-7.1.1
716+
return (self.keySizeInBits / 8) - (2 * Digest.hashBitLength / 8) - 2
708717
}
709718
}
710719

Tests/CryptoExtrasTests/TestRSAEncryption.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,35 @@ final class TestRSAEncryption: XCTestCase {
140140
XCTAssertEqual(primitives.publicExponent, e)
141141
}
142142
}
143+
144+
func testMaximumEncryptSize() throws {
145+
let pemRepresentation1024 = """
146+
-----BEGIN PUBLIC KEY-----
147+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTLbu1QZhtXWKCHOjavP5NUCwJ
148+
5DwjoMKGlEM/PQOMiY+wup8R1kCOHV6g+FvJ86laHJc0gqwFf1U51YxtQFy7cGV4
149+
W2zJeTkqadO2fvTCjbZU+Oa78iVtTynq5h4yRWrTmveyzInhdVpi075Ql2hpGuET
150+
H1qYVxqaDIJEHyETDQIDAQAB
151+
-----END PUBLIC KEY-----
152+
"""
153+
let pubKey1024 = try _RSA.Encryption.PublicKey(unsafePEMRepresentation: pemRepresentation1024)
154+
XCTAssertEqual(86, pubKey1024.maximumEncryptSize(with: .PKCS1_OAEP))
155+
XCTAssertEqual(62, pubKey1024.maximumEncryptSize(with: .PKCS1_OAEP_SHA256))
156+
157+
let pemRepresentation2048 = """
158+
-----BEGIN PUBLIC KEY-----
159+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8IRKcs5FrHlWye2lfwc
160+
Hr0Pi8g5iZhGMOOwmyIVsNULAvUIGZlIw38NNqebH3eF6ZxPiSRpwPwIs6QRcH5/
161+
IwbHkUc0KdBbUwXDrLs0w00I7Flu5RP7IEfkOZdDGEWFY1pA3H1HaogxKFc5k3mM
162+
s7pW6oty1eP4O7aVa/Pp363Vba7EZ2nru9lz4Ta+JU8UIHbpoddMGikGEKHrQ/Ge
163+
n9RMNzSIy/e7TgTwC39GKn8fwN6VfcdNjvIhJrFNha/ORNArpzup7FUUauGLKt3a
164+
jgsIjrAPBp63+Sy7+aFVoGTvI7DCkZ/Wv3JCFRuTAdYOa0A1xiqhTb1pcypvrd2T
165+
ZQIDAQAB
166+
-----END PUBLIC KEY-----
167+
"""
168+
let pubKey2048 = try _RSA.Encryption.PublicKey(pemRepresentation: pemRepresentation2048)
169+
XCTAssertEqual(214, pubKey2048.maximumEncryptSize(with: .PKCS1_OAEP))
170+
XCTAssertEqual(190, pubKey2048.maximumEncryptSize(with: .PKCS1_OAEP_SHA256))
171+
}
143172
}
144173

145174
struct RSAEncryptionOAEPTestGroup: Codable {

0 commit comments

Comments
 (0)