How can I decrypt Blowfish ciphertext with a salted header? - perl

I have some ciphertext that has been encrypted using Perl's Crypt::CBC module that I wish to decrypt elsewhere.
The ciphertext was generated using the 'simple' version of the Crypt::CBC constructor, that is:
use Crypt::CBC;
$cipher = Crypt::CBC->new( -key => 'my secret key',
-cipher => 'Blowfish'
);
From reading the MAN page, this method of construction will take the simple string key and random salt to generate an IV & literal key to use for encryption, as well as embed a header with the salt.
"salt" -- Combine the passphrase with
an 8-byte random value to
generate both the block cipher key and the IV from the
provided passphrase. The salt will be appended to the
beginning of the data stream allowing decryption to
regenerate both the key and IV given the correct passphrase.
This method is compatible with current versions of OpenSSL.
I now need to decrypt the ciphertext on another platform that only supports CBC decryption given the ciphertext, a literal key & IV. To attempt to generate the literal key, IV & salt, I used Crypt::CBC to generate the values like so:
my $crypt = new Crypt::CBC(-key => 'my secret key', -cipher => 'Blowfish');
my $out = $crypt->decrypt($ciphertext);
my $literal_key = $crypt->key();
my $iv = $crypt->iv();
my $salt = $crypt->salt();
The decryption here is correct, but I've been unable to use the generated literal key & IV to decrypt the cipher; this produces rubbish:
my $crypt2 = new Crypt::CBC(
-literal_key => 1,
-key => $literal_key,
-cipher => 'Blowfish',
-iv => $iv,
-header => 'none');
my $rubbish - $crypt2->decrypt($ciphertext);
I can't provide a literal key and use a salted header so I'm lost as to the next move.
How can I decrypt this text?
EDIT: The target system is not running Perl, but I have been able to generate the identical value as in $rubbish above, so I'm sure it's using the same algorithm (CBC, Blowfish) to decipher.

To decrypt the stream, you first need to remove the header added by Crypt::CBC's "salt" mode. The header consists of the 8 characters Salted__ followed by 8 bytes of salt data.
In perl, something like this should do it:
my $crypt2 = new Crypt::CBC(
-literal_key => 1,
-key => $literal_key,
-cipher => 'Blowfish',
-iv => $iv,
-header => 'none');
my $cleartext = $crypt2->decrypt(substr($ciphertext, 16));

This may work. Your key will have to be exactly 56 bytes in length and the iv will have to be exactly eight bytes long:
#!/usr/bin/perl
use strict;
use warnings;
use Crypt::CBC;
my $key = "x" x 56;
my $iv = "x" x 8;
my $plaintext = "this is just some normal text\n";
my $ciphertext = Crypt::CBC->new(
-cipher => 'Blowfish',
-header => 'none',
-literal_key => 1,
-key => $key,
-iv => $iv,
)->encrypt($plaintext);
print Crypt::CBC->new(
-cipher => 'Blowfish',
-header => 'none',
-literal_key => 1,
-key => $key,
-iv => $iv,
)->decrypt($ciphertext);

If anybody needs the _salted_key_and_iv function in PHP - here it is:
function _salted_key_and_iv ($pass, $salt) {
if(strlen($salt) != 8) {
die("Salt must be 8 bytes long");
}
$key_len = 56;
$iv_len = 8;
$desired_len = $key_len+$iv_len;
$data = '';
$d = '';
while (strlen($data) < $desired_len) {
$d = pack("H*", md5($d . $pass . $salt));
$data .= "$d";
}
return $data;
}

Related

How to AES decrypt a string in Powershell with a given IV and key as string?

I need to create a powershell code which sould decode an AES-128-CBC-encrypted string.
It needs to work with an IV and key in string-format.
Here is a demo with sample dataset from a public webpage:
# test-data from https://www.coderstool.com/aes-decryption
# should return 'CodersTool secret message'
$data = "Y+egIFaYXtHdRKFVg5h80Bn/6dECi5iiPgr2L9Bd8LY="
$iv = "rwj76dfsotja10tk"
$key = "nszxnqbq1s"
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = [byte[]][char[]]$key
$aes.IV = [byte[]][char[]]$IV
$aes.Padding = [System.Security.Cryptography.PaddingMode]::None
$dec = $aes.CreateDecryptor()
$result = $dec.TransformFinalBlock($data, 0, $data.Length)
$dec.Dispose()
But this code throws an error, that the key does not have the correct length.
What needs to be fixed to make the above sample work in Powershell and return the expected result?
The ciphertext has been generated using AES-128 in CBC mode and PKCS#7 padding.
The following must be changed in the code:
the ciphertext must be Base64 decoded,
the key must be padded from right with 0x00 values to a length of 16 bytes (AES-128),
the padding must be PKCS#7 (since PKCS#7 is the default, the line can be omitted),
the result has to be decoded with UTF-8.
The mode is set correctly, because CBC is the default:
$data = [Convert]::FromBase64String("Y+egIFaYXtHdRKFVg5h80Bn/6dECi5iiPgr2L9Bd8LY=")
$iv = "rwj76dfsotja10tk"
$key = "nszxnqbq1s".PadRight(16, [char]0)
$aes = [System.Security.Cryptography.Aes]::Create()
$utf8 = [System.Text.Encoding]::Utf8
$aes.Key = $utf8.GetBytes($key)
$aes.IV = $utf8.GetBytes($iv)
#$aes.Padding = [System.Security.Cryptography.PaddingMode]::none # change to PKCS7 or omit line, since PKCS7 is the default
$dec = $aes.CreateDecryptor()
$result = $dec.TransformFinalBlock($data, 0, $data.Length)
$resultStr = $utf8.GetString($result)
Write-Output $resultStr
$dec.Dispose()
Output:
CodersTool secret message

Conversion of public key parameters from Crypt::OpenSSL::RSA into JWK failed

I was creating a mock server for OAuth2 and I was stuck on during the generation of a JWK token, more specifically, I failed to understand how to convert the e and n parameters
I have this code:
use strict;
use v5.26.0;
use Data::Dumper; # or DDP if have i t installed!
use Crypt::JWT qw(encode_jwt decode_jwt);
use Crypt::OpenSSL::RSA;
use Crypt::OpenSSL::Bignum; # needs to be installed in order to run $rsa_pub->get_key_parameters();
use MIME::Base64;
# don't worry, this is just a test/mock server with 'random' generated RSA pair
my $rsa_pvt = Crypt::OpenSSL::RSA->new_private_key(
'-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0LdLaW9i0NUVwV1+ZSnwwYebLliFLCEQjzsklGww0fYa0VWM
kEl/WD1mam/idqqAadkgMKzZY4JWx824rRpHOosTJbOMHKa1cRA933yv8Ywzahmx
ih9Jx/QjK2VHSlxTRq2cKN/E9S2VN64PBlBc2LsoAzM9M4r9X90KJFi3+VazjDZ+
9iWA5UCqrWcGrEy21ZVLF27Dm03STIewtV9goBn/7Gd5sMfDfnvMLKSK5ZRbGigt
fkjS91qZUiHjU+WXJrMS2mTr/W9fmAJ7R9jQ09wpJVqP+UOnFjL/0mesAQ5H4FTt
RoXhYiKF3zqckRkAwRuP3sv2nCGA8MoiZR5rFwIDAQABAoIBAFpoSz4shX04D+hm
ey2O8T6jYtC8f1MSL34bfEjeZHdOR2eNywllDMhIMGjCdjI4wM8YwhzTgobcGoMJ
1YkF7Pyq6WxXTcXLYKTNCEAaXowe0taOspzF2MvIMMPHZw4K1/exlAcQhtw9Fnm7
574waUdoKnjYZRZCimZP9OixlV9nKrD36lytd1HewwcD+nm0whMq4Ud5W9AyGwwM
xJR9++srSiqi6wpj5p1cP73UgpdOn293foATV3NBjTBjndk/q1IoO8ylv+rn2Pwi
iFYGHUIrLsBBBYYPclEggP09M/r4vLlVhMpvf+0UBOodRLk5QuGsPlk3ch/jfSpf
f1JNLYECgYEA6lf4yjim+U3LumPKYzhM0zsQf5Qvp0pGb/7XWw3WdWc3hcnJevMp
uL1k6Qyg3186K+loJqdrsDqOQhZO2BBA8TMcsgiiEk2pTl5InSIf/aedNzuClX0D
gxjgOTMsp+9G6YafcvzG+5gCuFM0ONqyyC+mOl55ICSF0HPgN7TaUhECgYEA5AEI
LoxwPs4fXmXmpNg/2JD8upwl8GdEiDs4yaY2kE+5ocvtaNn6qi3kO5cDIAmkUqFE
WwjFMD2sROROtpivrlb3+GJbrjbmY27KrEFd5RkNRCArZV+sUNt/s7RrvNOVvJ9z
e82xYrNBXhpFhnaI4oLSN9ZWjx+mroX2M65iwqcCgYEAzzForqLYPqQh9HI2hvNI
OZqHQ8VpPKfXDz5qef8KFlNkK831bdeAk+4gQk0AD37Kl/iONV7hP7cGADhpDW+R
e7CNNnubkENJ5hhGa2e4kTSZNDRRiIo3iLl9xhUQ7ooIUIDOiYQlCl2kSgSGr53t
ZEF83y6YOWsRRPSu0ZH9VYECgYA4A/zjmsM02uUgBv78Ptioty4wFo7HmkdfBNW1
zO0Y1U1w7637FZqc1rt83GP7KgNB/bbSerwfVveM0V55Q9fdiCZR0rBdg8VkZmLK
oSCVWWtF8nVW6YNnNhYQq2HQuVbPSYlQwD81VX7YxLGSEGse4y8MYs9PSGJl/Cl5
lv1SfwKBgFE8zTWFAnMOBczF0VCl0hpUaIknlTwvL6iGf5Vl4M4XBKE8KEkjE+gE
H7lxZ8XxxVHrvm++BZZk5uSmIWGUZlJuRNLQDiRxLqNOAaG12YGyg/eTJOzkvzfL
U8UQV8ROVyZR1Rw8ya9ksAmlDjy55VOXHT9pvHd/FjQMcBeh9lE8
-----END RSA PRIVATE KEY-----'
);
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key(
'-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA0LdLaW9i0NUVwV1+ZSnwwYebLliFLCEQjzsklGww0fYa0VWMkEl/
WD1mam/idqqAadkgMKzZY4JWx824rRpHOosTJbOMHKa1cRA933yv8Ywzahmxih9J
x/QjK2VHSlxTRq2cKN/E9S2VN64PBlBc2LsoAzM9M4r9X90KJFi3+VazjDZ+9iWA
5UCqrWcGrEy21ZVLF27Dm03STIewtV9goBn/7Gd5sMfDfnvMLKSK5ZRbGigtfkjS
91qZUiHjU+WXJrMS2mTr/W9fmAJ7R9jQ09wpJVqP+UOnFjL/0mesAQ5H4FTtRoXh
YiKF3zqckRkAwRuP3sv2nCGA8MoiZR5rFwIDAQAB
-----END RSA PUBLIC KEY-----'
);
my ($n, $e, #o) = $rsa_pub->get_key_parameters();
# I think something is wrong in this conversion
$n = encode_base64($n->to_bin(), '');
$e = encode_base64($e->to_bin(), '');
my $kids = {
"keys" => [
{
"kty" => "RSA",
"kid" => "rsa1",
"alg" => "RS256",
"use" => "sig",
"n" => $n,
"e" => $e
}
]
};
say "Keys parameters";
say Dumper($kids);
my $encoded = encode_jwt(
payload => {a => 'b'},
alg => 'RS256',
key => $rsa_pvt,
extra_headers => {'kid' => 'rsa1'},
);
say "Generated JWT: $encoded";
my $decoded_with_pub = decode_jwt(
token => $encoded,
key => $rsa_pub,
);
say "Decoded with pub certificate";
say Dumper($decoded_with_pub);
say "Trying to decode with certificate from parameters";
# do not returns,
# Use of uninitialized value in unpack at ~/perl5/lib/perl5/x86_64-linux-gnu-thread-multi/Crypt/PK/RSA.pm line 102.
my $decoded_with_kid = decode_jwt(
token => $encoded,
kid_keys => $kids,
);
The Crypt::JWT is working when I test with the production keys https://sso.staging.acesso.gov.br/jwk
The e parameter AQAB (same recommended exponent of 65537) is matching, so I don't know how else I should convert the n from BigInt into Base64 any different than the way I did
SEO:
Use of uninitialized value in unpack at ~/perl5/lib/perl5/x86_64-linux-gnu-thread-multi/Crypt/PK/RSA.pm line 102.

openssl_seal fails error:0E06D06C:configuration file routines:NCONF_get_string:no value

I am trying to seal/open a file. Encryption fails and the following error is generated.
error:0E06D06C:configuration file routines:NCONF_get_string:no value
Here's a code sample that can reproduce the issue
// Generate key pair and keep them safe...
$key = openssl_pkey_new([
'private_key_bits' => 4096,
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1',
]);
$privKey = null;
openssl_pkey_export($key, $privKey);
$pubKeyDetails = openssl_pkey_get_details($key);
$pubKey = $pubKeyDetails['key'];
openssl_free_key($key);
// Load the pubkey to encrypt
$key = openssl_pkey_get_public($pubKey);
$data = file_get_contents('of-some-pretty-large-file');
// ----- This here fails -----
if (openssl_seal($data, $sealed, $eKeys, [$key]) === false) {
echo "Encryption failed\n";
echo openssl_error_string() . "\n";
exit;
}
openssl_free_key($key);
$key = openssl_pkey_get_private($privKey);
if (openssl_open($sealed, $decryptedData, $eKeys[0], $key)) {
echo ($decryptedData === $data ? "Matched\n" : "Trash\n");
}
openssl_free_key($key);
The error message
error:0E06D06C:configuration file routines:NCONF_get_string:no value
is not caused by openssl_seal, but by openssl_pkey_new. This does not affect the functionality, i.e. the key is generated successfully, see here and here. This also applies to the posted code, which generates a private EC key in SEC1 format and a public key in X.509 format.
The PHP method openssl_seal is based on the OpenSSL functions EVP_SealInit, EVP_SealUpdate and EVP_SealFinal, here. In the corresponding OpenSSL documentation the following is described in the Notes-section:
The public key must be RSA because it is the only OpenSSL public key algorithm that supports key transport.
This means that openssl_seal only works with RSA or an RSA key. If the following is used in the posted code:
$key = openssl_pkey_new([
'private_key_bits' => 4096,
'private_key_type' => OPENSSL_KEYTYPE_RSA
]);
decryption and encryption work as expected.

Using Zend_Mail_Transport_Smtp with an MD5-Hashed value as password

I'd like to offer the users ob my web-application the possibility to send out emails using our smtp-server.
The password for the user accounts are md5-hased and the smtp-server is hashing the received values to check for the right username-password kobination.
Now i'm looking for a good way to set up Zend_Mail_Transport_Smtp - I obviously need the plain-text password and forward it to the smtp-server which then converts it to a md5-hash.
But that means that i have to store the users password somewhere in plaintext, which i'd like to avoid.
Are there any best practices on how to set up an webmailer using the zend framework?
The only idea i had was to save the unhashed password in a session (the user accounts in my application are linked with the mail server accounts), but there has to be a better way to handle this situation
What you can do is to store the password in a encoded format in the database and decode it in your application when you need it. Unfortunately MD5 is just a hashing function and you cannot decode to the plain password. I know three ways to accomplish this:
Substitute letters:
You can use something like ROT13 to replace letters in your plain password:
// store this in the database
$pw_rot = str_rot13( "plain_password" );
// use this in the application
$pw_plain = str_rot13( "cynva_cnffjbeq" );
I wouldn't recommend to use str_rot13() or something like this, because is easily guessed by someone who sees the password.
Decode/encode without a key:
Another way is to decode/encode the password with a function, which doesn't need a key like Base64:
// store this in the database
$pw_base64 = base64_encode( "plain_password" );
// use this in the application
$pw_plain = base64_encode( "cGxhaW5fcGFzc3dvcmQ=" );
A little bit better then the above, but I would use that only for testing purposes, because it's easily implemented and to use.
Decode/encode with a key:
A better way is to use key and a symmetric block cipher like Blowfish:
class Password {
const KEY = 'your_secret_key_for_the_cipher';
// encode the plain text with key for storing in the database
public function encode( $plain_text ) {
// set up the environment
$td = mcrypt_module_open( MCRYPT_BLOWFISH, '', MCRYPT_MODE_ECB, '' );
$key = substr( self::KEY, 0, mcrypt_enc_get_key_size( $td ) );
$iv_size = mcrypt_enc_get_iv_size( $td );
$iv = mcrypt_create_iv( $iv_size, MCRYPT_RAND );
if( mcrypt_generic_init( $td, $key, $iv ) != -1 ) {
$cipher_text = mcrypt_generic( $td, $plain_text );
// clean up the mcrypt enviroment
mcrypt_generic_deinit( $td );
mcrypt_module_close( $td );
}
// use hex value
return bin2hex( $cipher_text );
}
// decode the stored cipher text with key to use in the application
public function decode( $cipher_text ) {
// set up the environment
$td = mcrypt_module_open( MCRYPT_BLOWFISH, '', MCRYPT_MODE_ECB, '' );
$key = substr( self::KEY, 0, mcrypt_enc_get_key_size( $td ) );
$iv_size = mcrypt_enc_get_iv_size( $td );
$iv = mcrypt_create_iv( $iv_size, MCRYPT_RAND );
if( mcrypt_generic_init( $td, $key, $iv ) != -1 ) {
$plain_text = mdecrypt_generic( $td, pack( "H*" , $cipher_text ) );
// clean up the mcrypt environment
mcrypt_generic_deinit( $td );
mcrypt_module_close( $td );
}
// remove NUL which maybe added by padding the plain_text
return rtrim( $plain_text, "\0" );
}
With this way only someone who has access to the database and the source code can decode the password. On the down side you have a more complex application and little bit performance impact. Also you can other symmetric block cipher.
And the most important: Never store plain passwords.

base64-Encoding breaks smime-encrypted emaildata

I'm using Mime::Lite to create and send E-Mails. Now I need to add support for S/Mime-encryption and finally could encrypt my E-Mail (the only Perllib I could install seems broken, so I'm using a systemcall and openssl smime), but when I try to create a mime-object with it, the E-Mail will be broken as soon as I set the Content-Transfer-Encoding to base64. To make it even more curious, it happens only if I set it via $myMessage->attr. If I'm using the constructor ->new everything is fine, besides a little warning which I suppress by using MIME::Lite->quiet(1);
Is it a bug or my fault? Here are the two ways how I create the mime-object.
Setting the Content-Transfer-Encoding via construtor and suppress the warning:
MIME::Lite->quiet(1);
my $msgEncr = MIME::Lite->new(From =>'me#myhost.com',
To => 'you#yourhost.com',
Subject => 'SMIME Test',
Data => $myEncryptedMessage,
'Content-Transfer-Encoding' => 'base64');
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->send;
MIME::Lite->quiet(0);
Setting the Content-Transfer-Encoding via $myMessage->attr which breaks the encrypted Data, but won't cause a warning:
my $msgEncr = MIME::Lite->new(From => 'me#myhost.com',
To => 'you#yourhost.com',
Subject => 'SMIME Test',
Data => $myEncryptedMessage);
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->attr('Content-Transfer-Encoding' => 'base64');
$msgEncr->send;
I just don't get why my message is broken when I'm using the attribute-setter. Thanks in advance for your help!
Besides that i'm unable to attach any file to this E-Mail without breaking the encrypted message again.
To debug this
Make a script call showmail.pl
#!/usr/bin/perl
while (<STDIN>) { print $_; }
Test it like
use MIME::Lite;
use Net::SMTP;
use MIME::Base64;
$myEncryptedMessage = encode_base64("This is not valid encrypted message\n");
MIME::Lite->send('sendmail', "./showmail.pl"); ## Add this for debugging.
MIME::Lite->quiet(1); my $msgEncr = MIME::Lite->new(From =>'me#localhost',
To => 'you#localhost',
Subject => 'SMIME Test',
Data => $myEncryptedMessage,
'Content-Transfer-Encoding' => 'base64');
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->send();
you should see something like.
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Length: 49
Content-Type: application/x-pkcs7-mime; name="smime.p7m"; smime-type="enveloped-data"
X-Mailer: MIME::Lite 3.028 (F2.74; B3.07; Q3.07)
Date: Mon, 23 Mar 2012 10:40:51 -0400
From: me#localhost
To: you#localhost
Subject: SMIME Test
Content-Transfer-Encoding: base64
VGhpcyBpcyBub3QgdmFsaWQgZW5jcnlwdGVkIG1lc3NhZ2UK
The message is encoded base64, but the real message still needs to be correctly
encypted. You need to make sure that is the case since $myEncryptedMessage is
passed in. With the debug output, you can compare with a known good encrypted mail
and see if the headers are good, as far as I can see the headers are fine, it is probably
the data that is not valid.
I am not able to test this with a real mail client, but this is what I think may work for multi-parts.
use MIME::Lite;
use Net::SMTP;
use MIME::Base64;
MIME::Lite->send('sendmail', "./showmail.pl"); ## <---- for testing only
my $from_address = "nobody#localhost";
my $to_address = "somebody#localhost";
my $mail_host = "localhost";
my $subject = "Subject list";
my $message_body = "Attachment list";
my #files = ("crypt.data1","crypt.data2");
$msg = MIME::Lite->new (
From => $from_address,
To => $to_address,
Subject => $subject,
Type =>'multipart/mixed'
) or die "Error creating multipart container: $!\n";
foreach $c(#files) {
$msg->attach (
Disposition => 'attachment',
Type => "application/x-pkcs7-mime; name=smime.p7m; smime-type=enveloped-data",
Path => $c,
) or die "Error adding $c: $!\n";
}
$msg->send;
As I said in one comment the difference in setting the encoding in the construtor of the mimeobject or with the ->attr-Setter is, that the construtor just sets the encoding in the mimeheader. By using the ->attr-Setter mime encodes the data with base64.
So in my case, my previously generated mimeobject - which is base64-encoded and with s/mime encrypted - read from a file needs to set the encoding in the construtor (and suppress the warning) so no more encoding will be done by mime. Otherwise mime will encode the data again and therefore break the encryption and the email itself.
I finally got attachments to work. To achieve this I create a normal multipart/mixed mimeobject, print this object into a normal file, encrypt this file with openssl smime, read this whole file (except the 6 headerlines) into a variable and use this as the datainput. Additionally I set the Content-Transfer-Encoding to base64 using the construtor (so no encoding is done to my data).
I hope this will help someone else then me ;)
Replace $myEncryptedMessage with encode_base64($myEncryptedMessage)
and use MIME::Base64;