std/signature/rsa
std/signature/src/rsa.trb
RSA signature verification of RFC 8017: RSASSA-PKCS1-v1_5 (section 8.2) and RSASSA-PSS (section 8.1) with SHA-256,
what JOSE calls RS256 and PS256 (RFC 7518 sections 3.3 and 3.5). Only the public operation - signature ** exponent mod modulus - is here, so nothing it computes is secret and it takes variable time; there is no private key
and no signing.
The arithmetic is ordinary TorbScript over Int: a number is a list of 16-bit limbs, least significant first, so a
product of two limbs plus the carries stays far below 2^63, and the modular multiplication is Montgomery's, which
needs no division. The limbs are 16 bits and not the 30 of ./montgomery because an RSA modulus has thousands of bits
and the modulus is only known at run time. Both back ends compute the same bits.
type RsaPublicKey
type RsaPublicKey with Show, Equals, Hash
A public RSA key: the modulus n and the public exponent e, kept as unsigned big-endian bytes without leading zeros
(a JSON Web Key's n and e, RFC 7518 section 6.3.1, or a COSE key's). Made only by RsaPublicKey.fromComponents,
which refuses what no verification should accept: a modulus under 2048 bits or even, an exponent below 3 or even.
Examples
// A 2048-bit modulus (all bits set but the lowest of the top byte) and the usual exponent 65537
const modulus: Bytes = Buffer.from List.filled(256, 255)
const key = RsaPublicKey.fromComponents modulus, [1, 0, 1]
print key.isOk()
print key.expect("a key").verifiesPkcs1Sha256("hello".bytes(), modulus)
Pitfalls
- Verification takes variable time. That is right for a public key and a public signature; do not use this for anything secret.
- Keys under 2048 bits are refused, however a protocol would have them: they are not safe, and RS256 of RFC 7518 section 3.3 requires 2048 bits at least.
- A signature has to be exactly as long as the modulus: JOSE and X.509 both write it that way, and a shorter or longer one is not "the same number" here but a signature that does not verify.
fn fromComponents
static fn fromComponents(modulus: Bytes, exponent: Bytes): Result<Self, SignatureError>
The key of a modulus and an exponent, unsigned big-endian bytes, leading zeros allowed (a JSON Web Key's n and
e after Base64url).
Errors
SignatureError.NotAnRsaKeyfor a modulus of fewer than 2048 bits or an even one, and for an exponent below 3 or an even one.
fn modulus
fn modulus(): Bytes
The modulus n: unsigned big-endian bytes without leading zeros.
fn exponent
fn exponent(): Bytes
The public exponent e: unsigned big-endian bytes without leading zeros.
fn bits
fn bits(): Int
The size of the modulus in bits: eight times the bytes of a signature, less the unused top bits of its first.
fn show
fn show(): String
One line: the size of the key and its exponent.
fn equals
fn equals(other: Self): Bool
Whether the two keys have the same modulus and the same exponent.
fn hash
fn hash(): Int
The hash of the modulus and the exponent, so that a key is a key of a Map or a member of a Set.
fn verifiesPkcs1Sha256
fn verifiesPkcs1Sha256(message: Bytes, signature: Bytes): Bool
Whether signature is this key's RSASSA-PKCS1-v1_5 signature with SHA-256 over message (RFC 8017 section 8.2.2):
what JOSE calls RS256. The signature, as many bytes as the modulus, is raised to the exponent modulo n and the
result compared whole with the one encoding of the hash (section 9.2) - 00 01 FF ... FF 00, the DigestInfo,
the hash - which is what rules out the forgeries of a parser that tolerates other paddings or trailing bytes.
A signature of the wrong length or not below the modulus does not verify. It takes variable time - everything it reads is public.
Examples
const modulus: Bytes = Buffer.from List.filled(256, 255)
const key = RsaPublicKey.fromComponents(modulus, [1, 0, 1]).expect("a key")
print key.verifiesPkcs1Sha256("hello".bytes(), modulus)
Pitfalls
- Only SHA-256: a signature over SHA-384 or SHA-512 (
RS384,RS512) does not verify.
fn verifiesPssSha256
fn verifiesPssSha256(message: Bytes, signature: Bytes): Bool
Whether signature is this key's RSASSA-PSS signature with SHA-256, MGF1 with SHA-256 and a salt of 32 bytes over
message (RFC 8017 sections 8.1.2 and 9.1.2): what JOSE calls PS256, which fixes the salt length (RFC 7518
section 3.5). The encoded message is taken apart whole: the trailer byte BC, the unused top bits, the zero padding
and the 01 in front of the salt are each checked, and the hash is recomputed and compared.
A signature of the wrong length or not below the modulus does not verify. It takes variable time - everything it reads is public.
Examples
const modulus: Bytes = Buffer.from List.filled(256, 255)
const key = RsaPublicKey.fromComponents(modulus, [1, 0, 1]).expect("a key")
print key.verifiesPssSha256("hello".bytes(), modulus)
Pitfalls
- Only a salt of 32 bytes: a signature made with another salt length (OpenSSL's default for
-pssis the hash length, but "maximum" is common too) does not verify.