module RSA ## # An RSA key pair. # # Refer to PKCS #1 v2.1, section 3, pp. 6-8. # # @see http://www.rsa.com/rsalabs/node.asp?id=2125 # @see http://en.wikipedia.org/wiki/Public-key_cryptography class KeyPair ## # The RSA private key. # # @return [Key] attr_accessor :private_key alias_method :private, :private_key ## # The RSA public key. # # @return [Key] attr_accessor :public_key alias_method :public, :public_key ## # Initializes a new key pair. # # @param [Key] private_key # @param [Key] public_key # @param [Hash{Symbol => Object}] options def initialize(private_key, public_key, options = {}) @private_key = private_key @public_key = public_key @options = options.dup end ## # Returns `true` if this key pair contains a private key. # # @return [Boolean] def private_key? !!private_key end alias_method :private?, :private_key? # for OpenSSL compatibility ## # Returns `true` if this key pair contains a public key. # # @return [Boolean] def public_key? !!public_key end alias_method :public?, :public_key? # for OpenSSL compatibility ## # Returns `true` if this is a valid RSA key pair according to # {RSA::PKCS1 PKCS #1}. # # @return [Boolean] # @see Key#valid? def valid? private_key.valid? && public_key.valid? end ## # Returns the byte size of this key pair. # # @return [Integer] def bytesize Math.log256(modulus).ceil end ## # Returns the bit size of this key pair. # # @return [Integer] def bitsize Math.log2(modulus).ceil end alias_method :size, :bitsize ## # Returns the RSA modulus for this key pair. # # @return [Integer] def modulus private_key ? private_key.modulus : public_key.modulus end alias_method :n, :modulus ## # Returns a hash table representation of this key pair. # # @example # key_pair.to_hash #=> {:n => ..., :d => ..., :e => ...} # # @return [Hash] def to_hash {:n => modulus, :d => private_key ? private_key.exponent : nil, :e => public_key ? public_key.exponent : nil} end ## # Encrypts the given `plaintext` using the public key from this key # pair. # # @overload encrypt(plaintext, options = {}) # @param [Integer] plaintext # @param [Hash{Symbol => Object}] options # @return [Integer] # # @overload encrypt(plaintext, options = {}) # @param [String, IO, StringIO] plaintext # @param [Hash{Symbol => Object}] options # @return [String] # # @param [Object] plaintext # @param [Hash{Symbol => Object}] options # @option options [Symbol, #to_sym] :padding (nil) def encrypt(plaintext, options = {}) case plaintext when Integer then encrypt_integer(plaintext, options) when String then PKCS1.i2osp(encrypt_integer(PKCS1.os2ip(plaintext), options)) when StringIO, IO then PKCS1.i2osp(encrypt_integer(PKCS1.os2ip(plaintext.read), options)) else raise ArgumentError, plaintext.inspect # FIXME end end ## # Decrypts the given `ciphertext` using the private key from this key # pair. # # @overload decrypt(ciphertext, options = {}) # @param [Integer] ciphertext # @param [Hash{Symbol => Object}] options # @return [Integer] # # @overload decrypt(ciphertext, options = {}) # @param [String, IO, StringIO] ciphertext # @param [Hash{Symbol => Object}] options # @return [String] # # @param [Object] ciphertext # @param [Hash{Symbol => Object}] options # @option options [Symbol, #to_sym] :padding (nil) def decrypt(ciphertext, options = {}) case ciphertext when Integer then decrypt_integer(ciphertext, options) when String then PKCS1.i2osp(decrypt_integer(PKCS1.os2ip(ciphertext), options)) when StringIO, IO then PKCS1.i2osp(decrypt_integer(PKCS1.os2ip(ciphertext.read), options)) else raise ArgumentError, ciphertext.inspect # FIXME end end ## # Signs the given `plaintext` using the private key from this key pair. # # @overload sign(plaintext, options = {}) # @param [Integer] plaintext # @param [Hash{Symbol => Object}] options # @return [Integer] # # @overload sign(plaintext, options = {}) # @param [String, IO, StringIO] plaintext # @param [Hash{Symbol => Object}] options # @return [String] # # @param [Object] plaintext # @param [Hash{Symbol => Object}] options # @option options [Symbol, #to_sym] :padding (nil) def sign(plaintext, options = {}) case plaintext when Integer then sign_integer(plaintext, options) when String then PKCS1.i2osp(sign_integer(PKCS1.os2ip(plaintext), options)) when StringIO, IO then PKCS1.i2osp(sign_integer(PKCS1.os2ip(plaintext.read), options)) else raise ArgumentError, plaintext.inspect # FIXME end end ## # Verifies the given `signature` using the public key from this key # pair. # # @overload verify(signature, plaintext, options = {}) # @param [Integer] signature # @param [Integer] plaintext # @param [Hash{Symbol => Object}] options # @return [Boolean] # # @overload verify(signature, plaintext, options = {}) # @param [String, IO, StringIO] signature # @param [String, IO, StringIO] plaintext # @param [Hash{Symbol => Object}] options # @return [Boolean] # # @param [Object] signature # @param [Object] plaintext # @param [Hash{Symbol => Object}] options # @option options [Symbol, #to_sym] :padding (nil) # @return [Boolean] def verify(signature, plaintext, options = {}) signature = case signature when Integer then signature when String then PKCS1.os2ip(signature) when StringIO, IO then PKCS1.os2ip(signature.read) else raise ArgumentError, signature.inspect # FIXME end plaintext = case plaintext when Integer then plaintext when String then PKCS1.os2ip(plaintext) when StringIO, IO then PKCS1.os2ip(plaintext.read) else raise ArgumentError, plaintext.inspect # FIXME end verify_integer(signature, plaintext, options) end protected ## # @private def encrypt_integer(plaintext, options = {}) PKCS1.rsaep(public_key, plaintext) end ## # @private def decrypt_integer(ciphertext, options = {}) PKCS1.rsadp(private_key, ciphertext) end ## # @private def sign_integer(plaintext, options = {}) PKCS1.rsasp1(private_key, plaintext) end ## # @private def verify_integer(signature, plaintext, options = {}) PKCS1.rsavp1(public_key, signature).eql?(plaintext) end end # class KeyPair end # module RSA