Fixed
Details
Priority
MinorAssignee
AlexAlexReporter
AlexAlexDeveloper
AlexAlexReviewer
Erik S.Erik S.Change Log Group
AddedChange Log Message
Added classes for performing secure encryption/decryption and random value generationPatch Instructions
Patches must be submitted through Phabricator.
To submit patch via Command Line use Patches Workflow (via Arcanist) tutorial.
To submit patch via Web Interface use Patches Workflow (via Web Interface) tutorial.
Time tracking
6h 42m logged5h 20m remainingStory Points
3Original estimate
Fix versions
Details
Details
Priority
Assignee
Alex
AlexReporter
Alex
AlexDeveloper
Alex
AlexReviewer
Erik S.
Erik S.Change Log Group
Added
Change Log Message
Added classes for performing secure encryption/decryption and random value generation
Patch Instructions
Patches must be submitted through Phabricator.
To submit patch via Command Line use Patches Workflow (via Arcanist) tutorial.
To submit patch via Web Interface use Patches Workflow (via Web Interface) tutorial.
Time tracking
Story Points
3
Original estimate
Fix versions
Created November 13, 2018 at 3:54 PM
Updated December 29, 2024 at 8:49 PM
Resolved August 29, 2022 at 7:49 AM
Plan (part 1 - dependencies): - 0.5h (sum)
add https://github.com/paragonie/random_compat Composer dependency (polyfill for "
random_bytes
" function added in PHP 7.0)add https://github.com/symfony/polyfill-php55 Composer dependency (polyfill for "
hash_pbkdf2
" function added in PHP 5.5)add https://github.com/symfony/polyfill-php56 Composer dependency (polyfill for "
hash_equals
" function added in PHP 5.6)Plan (part 2 - CSPRNG): - 3.5h (sum)
create final "
*SecurityGeneratorPromise*
" class with:0.2h
these protected properties:
"
$method
""
$arguments = array()
""
$resolvedValue
""
$asSignature = false;
""
$signatureRawOutput = false;
""
$signatureKeyOverride
"these constants:
"
TYPE_NUMBER
" = 1"
TYPE_STRING
" = 2"
TYPE_BYTES
" = 3add "
*SecurityGeneratorPromise::__construct($type, array $arguments = array())*
" method, that will:0.3h
have mapping between "self::TYPE_" constants and actual "resolve" methods
get {{'resolve*' }}method from mapping and throw exception if not found
assign arguments to corresponding class properties
add public "
*SecurityGeneratorPromise::__toString*
" method, that will:0.1h
if the "
$this->resolvedValue
" property isn't empty take it's valueif the "
$this->resolvedValue
" property is empty:call
$this->method
with$this->arguments
store result in "
$this->resolvedValue
" propertyif "
$this->asSignature
" is set to "false
" then return "$this->resolvedValue
" propertyif "
$this->asSignature
" is set to "true
" then return "SecurityEncryptor::createSignature($this->resolvedValue, $this->signatureRawOutput, $this->signatureKeyOverride)
" resultadd public "
*SecurityGeneratorPromise**::forDatabase(**$prefix_or_table, $column**)*
" method, that will:0.4h
typecast
$this
into string (would internally call "__toString
" method)if "
$prefix_or_table
" is unit config prefix, then get database table name from itquery database table column for generated string
if match found, then clear "
$this->resolvedValue
" property & repeat requestif no match found return the value
add public "
SecurityGeneratorPromise::asSignature($raw_output = false, $key_override = null)
" method, that will:0.3h
set "
$this->asSignature
" property to "true
"set "
$this->signatureRawOutput
" property to "$raw_output
" parameter valueset "
$this->signatureKeyOverride
" property to "$key_override
" parameter valueadd public "
SecurityGeneratorPromise::asValue()
" method, that will:0.2h
set "
$this->asSignature
" property to "false
"set "
$this->signatureRawOutput
" property to "false
"set "
$this->signatureKeyOverride
" property to "null
"add protected "
SecurityGeneratorPromise::resolveNumber($min, $max)
" method, that will return result of calling "random_int" function0.3h
from "
\RandomLib\Generator
" class (see https://github.com/ircmaxell/RandomLib/blob/master/lib/RandomLib/Generator.php):0.5h
copy all class constants
copy "
\RandomLib\Generator::$charArrays
" propertycopy "
\RandomLib\Generator::expandCharacterSets
" methodcopy "
UserHelper::generateRandomString
" method from In-Portal 5.3.x and for it:0.5h
rename into protected "
SecurityGeneratorPromise::resolveString
" methodas "
\RandomLib\Generator::generateString
" method:change signature into "
$length, $characters = ''
"call the "
SecurityGeneratorPromise::expandCharacterSets
"replace call to "
\SecurityGeneratorPromise::_generateRandomNumber
" method with "SecurityGeneratorPromise::generateNumber
"rename "
$password
" local variable into "$ret
"add protected "
SecurityGenerator::resolveBytes($length = 16, $raw_output = false)
" function (see "generateToken" function on https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence page), that would:0.5h
generate secure random token by calling
random_bytes($length)
when "
$raw_output
" parameter is set to "false
" wrap result with "bin2hex
" function call (would double result length)return the result
when $raw_output = false, then result is hex-encoded = twice as long as requested length
create final "
SecurityGenerator
" class0.2h
add these public methods, that would create "SecurityGeneratorPromise" with given arguments and this type:
0.5h
"
SecurityGenerator::generateNumber($min, $max)
" -SecurityGeneratorPromise::TYPE_NUMBER
"
*SecurityGenerator**::generateString($length, $characters = '')*
" -SecurityGeneratorPromise::TYPE_STRING
"*SecurityGenerator**::generateBytes($length = 16, $raw_output = false)*
" -SecurityGeneratorPromise::TYPE_BYTES
Plan (part 3 - encryption) - 4.5h (sum)
create "
SecurityEncryptor
" class0.2h
add these constants:
0.3h
"
SecurityEncryptor::HASHING_ALGORITHM
" constant with "sha256" value"
SecurityEncryptor::HASHING_KEY_LENGTH
" constant with "*32*
" value"
SecurityEncryptor::ENCRYPTION_METHOD
" constant with "*aes-128-cbc*
" value (or "*aes-256-cbc*
" value)"
SecurityEncryptor::ENCRYPTION_KEY_LENGTH
" constant with "*16*
" value (or "*32*
" value)on the "
System Requirements
" step during In-Portal installation/upgrade:0.5h
require "
openssl
" extension (check for "openssl_encrypt
" function) to be presentrequire cipher presence (call "
SecurityEncryptor::cipherAvailable
" method)during installation/upgrade (the "
\kInstallator::Run
" method and "sys_config
" step), when missing in the "/system/config.php
" add these settings:0.5h
"
SecurityHmacKey
" with value generated via "SecurityGenerator::generateString(self::HASHING_KEY_LENGTH, self::CHAR_ALNUM | self::CHAR_SYMBOLS)
" method call"
SecurityEncryptionKey
" with value generated via "SecurityGenerator::generateBytes(self::ENCRYPTION_KEY_LENGTH)
" method calladd public final "
SecurityEncryptor::derivateKey($key, $length)
" method, that will:0.1h
return
hash_pbkdf2(self::HASHING_ALGORITHM, $string, SecurityGenerator::generateBytes(16, true), 80000, $length, true);
add protected "
SecurityEncryptor::doGetHmacKey($key_override = null)
" method, that will:0.2h
if "$key_override" parameter is specified, then return it's value
otherwise return value of "
SecurityHmacKey
" setting from "/system/config.php
"add private "
SecurityEncryptor::_getHmacKey($key_override = null)
" method, that will:0.2h
call "
$this->doGetHmacKey($key_override)
" method and assign result to "$ret
" variableif "
$ret
" variable value is empty (''
) or missing (false
) throw an exceptionif "
$ret
" variable value length isself::HASHING_KEY_LENGTH
, then return itreturn
$this->derivateKey($ret, self::HASHING_KEY_LENGTH)
add public final "
SecurityEncryptor::createSignature($string, $raw_output = false, $key_override = null)
" method, that would:0.1h
return
hash_hmac(self::HASHING_ALGORITHM, $string, $this->_getHmacKey($key_override), $raw_output);
if $raw_data = false, then response is 32 symbols long
if $raw_data = true, then response is 64 symbols long
add protected "
SecurityEncryptor::doGetEncryptionKey($key_override = null)
" method, that will:0.2h
if "
$key_override
" parameter is specified, then return it's valueotherwise:
get value of "
SecurityEncryptionKey
" value (expected 32 bytes, because it's in hex form) from "/system/config.php
" into a "$ret"
variableconvert it from hex to binary form using
pack('H*', $hex_string)
return it
add private "
SecurityEncryptor::_getEncryptionKey($key_override = null)
" method, that will:0.2h
call "
$this->doGetEncryptionKey($key_override)
" method and assign result to "$ret
" variableif "
$ret
" variable value is empty (''
) or missing (false
) throw an exceptionif "
$ret
" variable value length is self::ENCRYPTION_KEY_LENGTH
, then return itreturn
$this->derivateKey($ret, self::ENCRYPTION_KEY_LENGTH)
add public final "
SecurityEncryptor*::encrypt*($plaintext, $key_override = null)
" and public final "SecurityEncryptor*::decrypt*($ciphertext, $key_override = null)
" methods like so:1.5h
take implementation from https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly page (the "
setLessUnsafeCookie
" and "getLessUnsafeCookie
" functions)replace "mcrypt" usage with "openssl" equivalent from https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong page (the methods of "
UnsafeOpensslAES
" class)use "
SecurityEncryptor::ENCRYPTION_METHOD
" instead of "aes-256-cbc
" cipheruse "
SecurityEncryptor::ENCRYPTION_KEY_LENGTH"
key length instead of 32 bytesuse "
$this->createSignature
" instead of calling "hash_hmac
" directlycall "
$this->_getEncryptionKey($key_override)
" to get encryption key (externally provided key can be used for re-encrypting data with different key)when decryption fails throw an exception right away
validate data during decryption process (e.g. https://github.com/defuse/php-encryption/blob/master/src/Crypto.php#L221) and throw an exception early as soon as we realize forgery attempt
add public final "
SecurityEncryptor*::cipherAvailable*()
" method, that will:0.5h
call "
openssl_get_cipher_methods
" functionreturn true, when "
self::ENCRYPTION_METHOD
" is present in above obtained arrayreturn
false
otherwiseQuote: 8.5h*1.4=12h