lib/encrypted_strings/extensions/string.rb in encrypted_strings-0.2.1 vs lib/encrypted_strings/extensions/string.rb in encrypted_strings-0.3.0

- old
+ new

@@ -1,210 +1,208 @@ require 'openssl' require 'base64' -module PluginAWeek #:nodoc: - module EncryptedStrings - module Extensions #:nodoc: - # Adds support for in-place encryption/decryption of strings - module String - def self.included(base) #:nodoc: - base.class_eval do - attr_accessor :cipher - - alias_method :equals_without_encryption, :== - alias_method :==, :equals_with_encryption - end - end - - # Encrypts the current string using the specified cipher. The default - # cipher is sha. - # - # Configuration options are cipher-specific. See each individual cipher - # class to find out the options available. - # - # == Example - # - # The following uses an SHA cipher to encrypt the string: - # - # password = 'shhhh' - # password.encrypt # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # - # == Custom encryption mode - # - # The following uses Symmetric cipher (with a default password) to - # encrypt the string: - # - # PluginAWeek::EncryptedStrings::SymmetricCipher.default_password = 'secret' - # password = 'shhhh' - # password.encrypt(:symmetric) # => "jDACXI5hMPI=\n" - # - # == Custom encryption options - # - # Some encryption modes also support additional configuration options - # that determine how to encrypt the string. For example, SHA supports - # a salt which seeds the algorithm: - # - # password = 'shhhh' - # password.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828" - def encrypt(*args) - cipher = cipher_from_args(*args) - encrypted_string = cipher.encrypt(self) - encrypted_string.cipher = cipher +module EncryptedStrings + module Extensions #:nodoc: + # Adds support for in-place encryption/decryption of strings + module String + def self.included(base) #:nodoc: + base.class_eval do + attr_accessor :cipher - encrypted_string + alias_method :equals_without_encryption, :== + alias_method :==, :equals_with_encryption end + end + + # Encrypts the current string using the specified cipher. The default + # cipher is sha. + # + # Configuration options are cipher-specific. See each individual cipher + # class to find out the options available. + # + # == Example + # + # The following uses an SHA cipher to encrypt the string: + # + # password = 'shhhh' + # password.encrypt # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # + # == Custom encryption mode + # + # The following uses Symmetric cipher (with a default password) to + # encrypt the string: + # + # EncryptedStrings::SymmetricCipher.default_password = 'secret' + # password = 'shhhh' + # password.encrypt(:symmetric) # => "jDACXI5hMPI=\n" + # + # == Custom encryption options + # + # Some encryption modes also support additional configuration options + # that determine how to encrypt the string. For example, SHA supports + # a salt which seeds the algorithm: + # + # password = 'shhhh' + # password.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828" + def encrypt(*args) + cipher = cipher_from_args(*args) + encrypted_string = cipher.encrypt(self) + encrypted_string.cipher = cipher - # Encrypts this string and replaces it with the encrypted value. This - # takes the same parameters as #encrypt, but returns the same string - # instead of a different one. - # - # == Example - # - # password = 'shhhh' - # password.encrypt!(:symmetric, :password => 'secret') # => "qSg8vOo6QfU=\n" - # password # => "qSg8vOo6QfU=\n" - def encrypt!(*args) - encrypted_string = encrypt(*args) - self.cipher = encrypted_string.cipher - - replace(encrypted_string) - end + encrypted_string + end + + # Encrypts this string and replaces it with the encrypted value. This + # takes the same parameters as #encrypt, but returns the same string + # instead of a different one. + # + # == Example + # + # password = 'shhhh' + # password.encrypt!(:symmetric, :password => 'secret') # => "qSg8vOo6QfU=\n" + # password # => "qSg8vOo6QfU=\n" + def encrypt!(*args) + encrypted_string = encrypt(*args) + self.cipher = encrypted_string.cipher - # Is this string encrypted? This will return true if the string is the - # result of a call to #encrypt or #encrypt!. - # - # == Example - # - # password = 'shhhh' - # password.encrypted? # => false - # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password.encrypted? # => true - def encrypted? - !cipher.nil? - end + replace(encrypted_string) + end + + # Is this string encrypted? This will return true if the string is the + # result of a call to #encrypt or #encrypt!. + # + # == Example + # + # password = 'shhhh' + # password.encrypted? # => false + # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password.encrypted? # => true + def encrypted? + !cipher.nil? + end + + # Decrypts this string. If this is not a string that was previously + # encrypted, the cipher must be specified in the same way that it is + # when encrypting a string. + # + # == Example + # + # Without being previously encrypted: + # + # password = "qSg8vOo6QfU=\n" + # password.decrypt(:symmetric, :password => 'secret') # => "shhhh" + # + # After being previously encrypted: + # + # password = 'shhhh' + # password.encrypt!(:symmetric, :password => 'secret') # => "qSg8vOo6QfU=\n" + # password.decrypt # => "shhhh" + def decrypt(*args) + raise ArgumentError, 'Cipher cannot be inferred: must specify it as an argument' if args.empty? && !encrypted? - # Decrypts this string. If this is not a string that was previously - # encrypted, the cipher must be specified in the same way that it is - # when encrypting a string. - # - # == Example - # - # Without being previously encrypted: - # - # password = "qSg8vOo6QfU=\n" - # password.decrypt(:symmetric, :password => 'secret') # => "shhhh" - # - # After being previously encrypted: - # - # password = 'shhhh' - # password.encrypt!(:symmetric, :password => 'secret') # => "qSg8vOo6QfU=\n" - # password.decrypt # => "shhhh" - def decrypt(*args) - raise ArgumentError, 'Cipher cannot be inferred: must specify it as an argument' if args.empty? && !encrypted? - - cipher = args.empty? && self.cipher || cipher_from_args(*args) - encrypted_string = cipher.decrypt(self) - encrypted_string.cipher = nil - - encrypted_string - end + cipher = args.empty? && self.cipher || cipher_from_args(*args) + encrypted_string = cipher.decrypt(self) + encrypted_string.cipher = nil - # Decrypts this string and replaces it with the decrypted value This - # takes the same parameters as #decrypt, but returns the same string - # instead of a different one. - # - # For example, - # - # password = "qSg8vOo6QfU=\n" - # password.decrypt!(:symmetric, :password => 'secret') # => "shhhh" - # password # => "shhhh" - def decrypt!(*args) - value = replace(decrypt(*args)) - self.cipher = nil - value - end - - # Can this string be decrypted? Strings can only be decrypted if they - # have previously been decrypted *and* the cipher supports decryption. - # To determine whether or not the cipher supports decryption, see the - # api for the cipher. - def can_decrypt? - encrypted? && cipher.can_decrypt? - end - - # Tests whether the other object is equal to this one. Encrypted strings - # will be tested not only on their encrypted strings, but also by - # decrypting them and running tests against the decrypted value. - # - # == Equality with strings - # - # password = 'shhhh' - # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password == "shhhh" # => true - # - # == Equality with encrypted strings - # - # password = 'shhhh' - # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password == 'shhhh' # => true - # - # another_password = 'shhhh' - # another_password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" - # password == another_password # => true - def equals_with_encryption(other) - if !(is_equal = equals_without_encryption(other)) && String === other - if encrypted? - if other.encrypted? - # We're both encrypted, so check if: - # (1) The other string is the encrypted value of this string - # (2) This string is the encrypted value of the other string - # (3) The other string is the encrypted value of this string, decrypted - # (4) This string is the encrypted value of the other string, decrypted - is_string_equal?(self, other) || is_string_equal?(other, self) || self.can_decrypt? && is_string_equal?(self.decrypt, other) || other.can_decrypt? && is_string_equal?(other.decrypt, self) - else - # Only we're encrypted - is_string_equal?(other, self) - end + encrypted_string + end + + # Decrypts this string and replaces it with the decrypted value This + # takes the same parameters as #decrypt, but returns the same string + # instead of a different one. + # + # For example, + # + # password = "qSg8vOo6QfU=\n" + # password.decrypt!(:symmetric, :password => 'secret') # => "shhhh" + # password # => "shhhh" + def decrypt!(*args) + value = replace(decrypt(*args)) + self.cipher = nil + value + end + + # Can this string be decrypted? Strings can only be decrypted if they + # have previously been decrypted *and* the cipher supports decryption. + # To determine whether or not the cipher supports decryption, see the + # api for the cipher. + def can_decrypt? + encrypted? && cipher.can_decrypt? + end + + # Tests whether the other object is equal to this one. Encrypted strings + # will be tested not only on their encrypted strings, but also by + # decrypting them and running tests against the decrypted value. + # + # == Equality with strings + # + # password = 'shhhh' + # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password == "shhhh" # => true + # + # == Equality with encrypted strings + # + # password = 'shhhh' + # password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password == 'shhhh' # => true + # + # another_password = 'shhhh' + # another_password.encrypt! # => "66c85d26dadde7e1db27e15a0776c921e27143bd" + # password == another_password # => true + def equals_with_encryption(other) + if !(is_equal = equals_without_encryption(other)) && String === other + if encrypted? + if other.encrypted? + # We're both encrypted, so check if: + # (1) The other string is the encrypted value of this string + # (2) This string is the encrypted value of the other string + # (3) The other string is the encrypted value of this string, decrypted + # (4) This string is the encrypted value of the other string, decrypted + is_string_equal?(self, other) || is_string_equal?(other, self) || self.can_decrypt? && is_string_equal?(self.decrypt, other) || other.can_decrypt? && is_string_equal?(other.decrypt, self) else - if other.encrypted? - # Only the other string is encrypted - is_string_equal?(self, other) - else - # Neither are encrypted and equality test didn't work before, so - # they can't be equal - false - end + # Only we're encrypted + is_string_equal?(other, self) end else - # The other value wasn't a string, so we can't check encryption equality - is_equal - end - end - - private - def is_string_equal?(value, encrypted_value) #:nodoc: - # If the encrypted value can be decrypted, then test against the decrypted value - if encrypted_value.can_decrypt? - encrypted_value.decrypt.equals_without_encryption(value) + if other.encrypted? + # Only the other string is encrypted + is_string_equal?(self, other) else - # Otherwise encrypt this value based on the cipher used on the encrypted value - # and test the equality of those strings - encrypted_value.equals_without_encryption(encrypted_value.cipher.encrypt(value)) + # Neither are encrypted and equality test didn't work before, so + # they can't be equal + false end end - - # Builds the cipher to use from the given arguments - def cipher_from_args(*args) #:nodoc: - options = args.last.is_a?(Hash) ? args.pop : {} - name = (args.first || :sha).to_s.gsub(/(?:^|_)(.)/) {$1.upcase} - PluginAWeek::EncryptedStrings.const_get("#{name}Cipher").new(options) - end + else + # The other value wasn't a string, so we can't check encryption equality + is_equal + end end + + private + def is_string_equal?(value, encrypted_value) #:nodoc: + # If the encrypted value can be decrypted, then test against the decrypted value + if encrypted_value.can_decrypt? + encrypted_value.decrypt.equals_without_encryption(value) + else + # Otherwise encrypt this value based on the cipher used on the encrypted value + # and test the equality of those strings + encrypted_value.equals_without_encryption(encrypted_value.cipher.encrypt(value)) + end + end + + # Builds the cipher to use from the given arguments + def cipher_from_args(*args) #:nodoc: + options = args.last.is_a?(Hash) ? args.pop : {} + name = (args.first || :sha).to_s.gsub(/(?:^|_)(.)/) {$1.upcase} + EncryptedStrings.const_get("#{name}Cipher").new(options) + end end end end ::String.class_eval do - include PluginAWeek::EncryptedStrings::Extensions::String + include EncryptedStrings::Extensions::String end