Can't validate Telegram Web App for Bots user - php

I've tried to do user verification script for telegram web app for bots. I have no idea how to fix it. The hash and encoded string are similar but not the same.
Telegram article (documentation): https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
$bot_token="5368097647:AAFU8jBho71pglrzDeGw6LawrVuMmxaxpEQ";
$data_check_string=$_POST['a'];
$data_check_string=explode("&", urldecode($data_check_string));
foreach ($data_check_string as &$arrvalue) {
$hash="";
if (substr($arrvalue, 0, 4)=='hash'){
$hash=explode("=", $arrvalue)[1];
break;
}
}
sort($data_check_string);
$data_check_string=implode('\\n', $data_check_string);
$secret_key = $sig = hash_hmac('sha256', $bot_token, "WebAppData");
if (hash_hmac('sha256', $data_check_string, $secret_key) == $hash) {
print("fromtg");
}
else{
print("notg");
}
?>

Join the array with implode("\n", $data_check_arr);
in the hash_mac functions set the fourth param to TRUE (outputs raw binary data);
before compare the two hash apply bin2hex function to calculated hash to convert binary data into hexadecimal representation;
Here my code:
<?php
$data_check_arr = explode('&', rawurldecode($data_check_string));
$needle = 'hash=';
$check_hash = FALSE;
foreach( $data_check_arr AS &$val ){
if( substr( $val, 0, strlen($needle) ) === $needle ){
$check_hash = substr_replace( $val, '', 0, strlen($needle) );
$val = NULL;
}
}
// if( $check_hash === FALSE ) return FALSE;
$data_check_arr = array_filter($data_check_arr);
sort($data_check_arr);
$data_check_string = implode("\n", $data_check_arr);
$secret_key = hash_hmac( 'sha256', $bot_token, "WebAppData", TRUE );
$hash = bin2hex( hash_hmac( 'sha256', $data_check_string, $secret_key, TRUE ) );
if( strcmp($hash, $check_hash) === 0 ){
// validation success
}else{
// validation failed
}

Related

Encryption function returns different Encrypted key?

I am using this function to encrypt a string :
function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = 'AES-256-ECB';
$secret_key = 'gT4ThOvcaFDccFFV21mg5hPzglZsY73T';
$key = hash('sha256', $secret_key);
if ( $action == 'encrypt' )
{
$output = openssl_encrypt($string, $encrypt_method, $key, 0);
$output = base64_encode($output);
}
else if( $action == 'decrypt' )
{
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0);
}
return $output;
}
I am using this function in 2 php files (same function no changes at all )
the only difference between the 2 files is :
File1.php is getting the string in HTTP GET request:
$Email = htmlspecialchars($_GET["Email"]);
$ENC_Email = encrypt_decrypt("encrypt",$Email);
echo($ENC_Email);
and File2.php is getting the very same string from cookies:
$Email = $_COOKIE['Email'];
$enc = encrypt_decrypt("encrypt",$Email);
echo($enc);
echo of the variable $Email is same in both cases , but the resulting encrypted string is different.
What am I missing here ?
It's normal. You encrypt a string, it's not the samething as hashing a string. A hash will give you always the same result. But encryption use random salt, randomIV and differents other random parameters. Then the result is always different.
But, when you decrypt, you must get the string you have encrypted before.

validate md5 base64_encode to show value

I am comparing two base64_encode md5 keys created with same values (IP,Time,Path & Password) on different domains.
Create Encrypted key on 1st domain
$secret = "PASSWORD";
$expires = time()+3600;
$uri = '/video1/';
$ip = $_SERVER['REMOTE_ADDR'];
$md5 = base64_encode(md5($secret . $expires . $uri . $ip, true));
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);
$rtmp = "?md5=".$md5;
$urls= 'http://example.com'.$uri .$rtmp;
echo '' .$urls . '';
Create Encrypted key on 2nd domain and compare with URL received Key
function getAddress() {
$protocol = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
return $protocol.'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
}
$url = getAddress();
$path = (parse_url($url, PHP_URL_PATH)); // recive path here '/video1/'
$verify = substr(parse_url($url, PHP_URL_QUERY),4); //recive md5 encoded key from URL
/* create again md5 encoded key to match with URL key */
$secret = "PASSWORD";
$expires = time()+3600;
$uri = $path;
$ip = $_SERVER['REMOTE_ADDR'];
$md5 = base64_encode(md5($secret . $expires . $uri . $ip, true));
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);
$rtmp = $md5;
if ($rtmp===$verify){ // Matching both, local key with URL key
echo '<h1>Welcome</h1>';
}
else {
echo '<h1>Password,Time,Path or IP Not Match</h1>';
}
I used time(3600) in Encryption, So if statement should be show value for 3600 second. But this always show else value.
How this will be print if value for time(3600)? After that time print else
Perhaps above and beyond the scope of the question but the whole MD5 approach was flawed so why not go the "whole hog" ( as the saying goes ) and actually use encryption rather than hashing as in the question?
The encrypt and decrypt functions are based upon code found in the PHP manual for openssl_encrypt
function encrypt( $data=false, $pubkey=false, $cipher='AES-128-CBC' ){
if( !empty( $data ) && in_array( $cipher, openssl_get_cipher_methods() ) ){
$ivlen = openssl_cipher_iv_length( $cipher );
$iv = openssl_random_pseudo_bytes( $ivlen );
$encrypted = openssl_encrypt( $data, $cipher, $pubkey, $options=OPENSSL_RAW_DATA, $iv );
$hash = makehash( $encrypted, $pubkey );
return base64_encode( $iv . $hash . $encrypted );
}
return false;
}
function decrypt( $data, $pubkey=false, $cipher='AES-128-CBC' ){
if( !empty( $data ) && in_array( $cipher, openssl_get_cipher_methods() ) ){
$shalength=32;
$data = base64_decode( $data );
$ivlen = openssl_cipher_iv_length( $cipher );
$iv = substr( $data, 0, $ivlen );
$hash = substr( $data, $ivlen, $shalength );
$encrypted = substr( $data, $ivlen + $shalength );
$decrypted = openssl_decrypt( $encrypted, $cipher, $pubkey, $options=OPENSSL_RAW_DATA, $iv );
if( $decrypted && hash_equals( $hash, makehash( $encrypted, $pubkey ) ) ){
return $decrypted;
}
}
return false;
}
function makehash( $data, $key ){
return hash_hmac( 'sha256', $data, $key, true );
}
Then, to use it:
$lifetime=3600;
$key='A complex secret string - ideally this will be the contents of an ssl cert perhaps obtained using file_get_contents etc';
$cipher='AES-128-CBC';
/* Create the payload of items to be encrypted and passed in the url */
$payload=array(
'endpoint' => '/secret-forum/topic404',
'expires' => time() + $lifetime,
'ip' => $_SERVER['REMOTE_ADDR']
);
/* create a nice string to be encrypted */
$data=urldecode( http_build_query( $payload ) );
/* create the encrypted data string */
$encrypted=encrypt( $data, $key, $cipher );
/* construct the url to be presented to the user */
$url=sprintf( '%s://%s/?hash=%s', $_SERVER['REQUEST_SCHEME'], $_SERVER['HTTP_HOST'], $encrypted );
printf('%1$s', $url);
/* At the Server - To process the url and check validity */
$querystring = parse_url( $url, PHP_URL_QUERY );
if( !empty( $querystring ) ){
list( $param, $data )=explode( '=', $querystring );
/* decrypt data */
$decrypted=decrypt( $data, $key, $cipher );
if( $decrypted ){
/* process initial querystring we created - create an array $out */
parse_str( $decrypted, $out );
/* for simplicity, cast as an object to use object notation */
$obj=(object)$out;
$endpoint=$obj->endpoint;
$expires=$obj->expires;
$ip=$obj->ip;
/* perform logic tests on the decrypted data and act accordingly */
if( time() > $expires or $ip!=$_SERVER['REMOTE_ADDR'] ){
/* too late */
printf( '<h1>That link has now expired</h1><p>You are no longer premitted to access that resource</p>' );
} else {
/* all good */
printf( '<h1>Welcome</h1><p>%s</p>', $obj->endpoint );
}
}
}
I haven't copied all parts of your code but this is the principle.
Hash the password and time separate, that way you can make sure the password is correct and look at the time independently.
$secret = "PASSWORD";
$expires = time()+3600;
$urls= 'http://example.com?md5=" . md5($secret) . "&t=" . md5($expires);
This will pass them independently and on the receiving end you match the password with password and you loop the time to see if it's valid.
if($_GET['md5'] == $password) $validM = true;
for($i = time()+3600; $i>time(); $i--){
if(md5($i) == $_GET['t']) $validT = true;
}
if($validM && $validT){
echo "within 3600 seconds and correct password";
}
This is a safe method for about 95% of the world's population, but since we are passing password and time variables by GET, it's not that hard to figure out how to get illegal access.
If it's something that needs safe transactions you are using this for then don't use this method.

Port PHP encryption/decryption to Node.js

I'm trying to port over the following code to Node.js with no luck.
$output = false;
$key = hash( 'sha256', self::SECRET_KEY );
$iv = substr( hash( 'sha256', self::SECRET_IV ), 0, 16 );
if( $action == 'e' ) {
$output = base64_encode( openssl_encrypt( $string, self::ENCRYPT_METHOD, $key, 0, $iv ) );
}
else if( $action == 'd' ){
$output = openssl_decrypt( base64_decode( $string ), self::ENCRYPT_METHOD, $key, 0, $iv );
}
return $output;
This is where I am so far, but it's either spitting out gibberish or I get one of several errors.
var crypto = require('crypto'),
algorithm = ENCRYPT_METHOD,
password = SECRET_IV;
function encrypt(text){
var cipher = crypto.createCipher(algorithm, sha256(SECRET_KEY), sha256(SECRET_IV).substring(0,16))
let p = Buffer.from(text, 'utf8').toString('base64');
var crypted = cipher.update(p,'utf8','base64');
crypted += cipher.final('base64');
return crypted;
}
function sha256(data) {
return crypto.createHash("sha256").update(data).digest("hex");
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm, sha256(SECRET_KEY), sha256(SECRET_IV).substring(0,16));
decipher.setAutoPadding(false);
var dec = decipher.update(text,'base64','utf-8');
dec += decipher.final('utf8');
return dec;
}
I've tried decoding/encoding with base64, but this also does not work. Once I added setAutoPaddding to false, I started making some progress. I found on one thread that this is an issue when encoding/decoding from one language on Node.
Any help would be appreciated.

Mimicking PHP openssl_encrypt() and openssl_decrypt() call in Perl

I am trying to write some Perl code to decode Triple DES (3DES) keys generated by a PHP program. I've looked for an example of using Crypt::GCrypt decoding in Perl but can't find one.
I need a Perl equivalent to PHP's openssl_decode(), but even an encrypt/decrypt of a string does not match.
This is my test code. All values are samples for testing.
use strict;
{
# match PHP's openssl_encrypt($plain_text, 'des3', $pw, 0, $iv );
# openssl_decrypt($cipher_text,'des3', $pw, 0, $iv );
# $ee = Exxx->new( %args );
# $cipher_text = $ee->encode( $plain_text );
# $plain_text = $ee->decode( $cipher_text );
package Exxx;
use Crypt::GCrypt;
use HTML::Entities;
use MIME::Base64;
my $ex_ = 0;
my $exxx_method = $ex_ ++;
my $exxx_password = $ex_ ++;
my $exxx_iv = $ex_ ++;
my $exxx_cipher = $ex_ ++;
sub new {
my ( $class, $method, $passwd, $iv ) = #_;
my $type = ref($class) ? ref($class) : __PACKAGE__;
my $this = [];
$this->[$exxx_method] = $method ? $method : '3des';
$this->[$exxx_method] = '3des' # map any PHP name to a Perl name
if $this->[$exxx_method] eq 'des3';
$this->[$exxx_password] = pack(
'H*',
$passwd ? $passwd
: '0123456789ABCDEF' . # key 1
'FEDCBA9876543210' . # key 2
'3243F6A8885A308D' ); # key 3
$this->[$exxx_iv] = pack(
'H*',
$iv ? $iv
: '2b7e151628aed2a6' );
bless $this, $type;
return $this;
}
sub _cipher {
my ( $this, $encrypting_decrypting ) = #_;
my $cipher = Crypt::GCrypt->new(
type => 'cipher',
algorithm => $this->[$exxx_method],
mode => 'cbc',
padding => 'standard',
);
$cipher->start($encrypting_decrypting);
$cipher->setiv( $this->[$exxx_iv] );
return $cipher;
}
sub encrypt {
my ( $this, $plain_text ) = #_;
my $cipher = $this->_cipher("encrypting");
$cipher->encrypt($plain_text);
my $encrypted = $cipher->finish();
return encode_base64( $encrypted, "" );
}
sub encrypt_html {
my ( $this, $plain_html ) = #_;
return $this->encrypt( decode_entities($plain_html) );
}
sub decrypt {
my ( $this, $crypt_text ) = #_;
my $cipher = $this->_cipher("decrypting");
my $plain_text = $cipher->decrypt( decode_base64($crypt_text) );
my $f = $cipher->finish();
return $plain_text;
}
}
############################
my $exit = 0;
my $plain = 'Hello, world'; #clear text for both PHP and PERL
my $encrypted_php = 'c0WJDTwtcBsj1vTfTi7jwA=='; #crypted from PHP program
my $exxx = Exxx->new('des3');
my $encrypted = $exxx->encrypt($plain);
if ( $encrypted eq $encrypted_php ) {
print "PASS encrypt: (perl)$encrypted eq (php)$encrypted_php\n";
}
else { #trouble.... does not match PHP
print STDERR "ERROR encrypt: (perl)$encrypted ne (php)$encrypted_php\n";
$exit = 1;
}
my $decrypted = $exxx->decrypt($encrypted);
if ( $plain eq $decrypted ) { #trouble.... does not match PHP
print "PASS decrypt: (perl)$plain == (perl)$decrypted\n";
$exit = 1;
}
{ #trouble.... does not match PHP
print STDERR "ERROR decrypt: (perl)$plain ne (perl)$decrypted\n";
$exit = 1;
}
exit $exit;
I recommend that you use the excellent Crypt::Cipher library suite. It covers a huge number of ciphers, it is independent of any other module, and I have compiled it successfully using Visual C as well as gcc and g++ without any issues
You will need to specify DES_EDE (same as Triple DES and 3DES) when you create a new Crypt::Cipher object, and the rest should be obvious
Crypt::Cipher->new('DES_EDE', $key);

Decrypt fails if content is only a zero (0) with openssl encrypt decrypt

I am using the following function for encrypt and decrypt of wordpress post content in a WordPress plugin:
public static function afz_encrypt_decrypt( $action, $string ) {
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = '12345678901234567890123456789012';
$iv_size = 16;
if ( $action == 'encrypt' ) {
$encrypt_iv = mcrypt_create_iv( $iv_size, MCRYPT_DEV_URANDOM );
$output = openssl_encrypt( $string, $encrypt_method, $secret_key, 0, $encrypt_iv );
$output = base64_encode( $encrypt_iv . $output );
} else if ( $action == 'decrypt' ) {
$decrypt_iv = substr( base64_decode( $string ), 0, $iv_size );
$output = stripslashes( openssl_decrypt( substr( base64_decode( $string ), $iv_size ), $encrypt_method,
$secret_key,
0,
$decrypt_iv ) );
if ( false == $output ) {
$output = $string;
}
}
return $output;
}
The above code fails to decrypt the text if the content is ONLY a zero character!!!
It returns the encoded string that encrypt produced.
Any ideas?
if ( false == $output ) {
$output = $string;
}
This checks if the output is "falsy" and if it is it sets $output back to the original encrypted string. "0" is falsy. Replace the check with
if ( false === $output ) {
To check if the output is actually false and not just falsy.
For a better explanation of what is and isn't falsy, see this documentation page.

Categories