BMF-RKSV-Technik / at-registrierkassen-mustercode

111 stars 39 forks source link

PHP: Probleme mit negativem Turnovercounter #483

Closed afill closed 7 years ago

afill commented 7 years ago

Habe derzeit noch Probleme mit einem negativen Summenspeicher. Bei positiven Werten laufen meine Tests problemlos durch:

`

     public function encryptMemory ($key_base64, $cashbox_id, $receipt_number, $turnover) {
    // base_convert() will treat your value as a string here,
    // converting it from decimal to hexadecimal
    $hexStringValue = base_convert($turnover, 10, 16);

    // Pad with zeros to the left, until pack()'s output length is matched
    $hexStringValue = str_pad($hexStringValue, 16, '0', STR_PAD_LEFT);

    // Convert to binary
    $tc_bin = hex2bin($hexStringValue);

    $key_bin = base64_decode($key_base64);
    $iv_bin = substr(hash('sha256', $cashbox_id . $receipt_number, true), 0, 16);
    $tc_encrypted_base64 = openssl_encrypt($tc_bin, 'AES-256-CTR', $key_bin, false, $iv_bin);

    return sprintf("%s", $tc_encrypted_base64);
}
public function decryptMemory ($key_base64, $cashbox_id, $receipt_number, $crypt) {
    $key_bin = base64_decode($key_base64);
    $iv_bin = substr(hash('sha256', $cashbox_id . $receipt_number, true), 0, 16);
    $result = openssl_decrypt($crypt, 'AES-256-CTR', $key_bin, OPENSSL_ZERO_PADDING, $iv_bin);

    for($i = 0; $i < strlen($result); $i++) {
        $ret[] = ord(substr($result, $i, 1 )  );
    }
    $nbytes = array_reverse($ret);

    $umsatz = ($nbytes[3]<<24) + ($nbytes[2]<<16) + ($nbytes[1]<<8) + $nbytes[0];
    return $umsatz;
}

`

Meine Werte zum Testen: $cashbox_id = 'b19e'; $key_base64 = 'LbEk2v8pMhiNaVHsj3wogDrdaXwhgIE3pshyzRdxUE0='; $receipt_number = 20200033; $turnover = -3080

Wenn ich das decrypte kommt 3080 heraus ...

Kann mir hier wer einen Tipp geben?

eauth commented 7 years ago

da sind ein paar Sachen nicht richtig, die Zahl muss als Bigendian gespeichert werden, und mit 16 Byte Länge für die Verschlüsselung. So funktionierts mit einem 5-Byte Umsatzzähler:

Encrypt: $tc_bin=str_pad(pack('N', $turnover>>8).chr($turnover),16,chr(0));

Decrypt:

$a=unpack("C*",$result);
$umsatz=$a[5]+($a[4]<<8)+($a[3]<<16)+($a[2]<<24)+($a[1]<<32);
if ($umsatz>0x7fffffffff) $umsatz-=0x10000000000;
lebail commented 7 years ago

@eauth: Dieser Code funktioniert für (betragsmäßig) größere negative Zahlen nicht. Mit z.B. $turnover = -1000000000000 kommt nach Hin- und Rückwärtskonvertierung $umsatz = +99511627776 heraus (auch auf Systemen, auf denen PHP 64-Bit-Integer unterstützt).

So klappt es aber (auch mit noch größeren Zahlen) - und ist sogar einfacher:

<?php
// integer to binary
$tc = -1000000000000000000;
$tc_bin = pack('J', $tc); // 'J' = 64-bit big-endian binary string

// binary to integer
$umsatz = unpack('J', $tc_bin);
echo 'Ergebnis: ', $umsatz[1], "\n";
?>

Ergebnis: -1000000000000000000

Eine "händische" Vorzeichenkorrektur ist nicht nötig (und würde sogar ein falsches Ergebnis erzeugen), da PHP ohnehin mit Zweierkomplement-Zahlen arbeitet.

@afill: Eine linksseitige Verlängerung auf 16 Byte ist für die Verschlüsselung nicht notwendig, da AES-CTR eine Stromchiffre darstellt, also byteweise arbeitet. Man kann auch nur einen Teil des 16-Byte-Blocks befüllen (z.B. die ersten N Byte) und erhält das Resultat dann ebenfalls in den ersten N Byte des 16-Byte-Blocks.

Je nach gewählter Umsatzzähler-Länge kann man N = 5, 6, 7 oder 8 benützen.

Achtung: Der Umsatzzähler kann nur dann als einfache Integer-Variable geführt werden, wenn PHP mit 64-bit-Integern arbeitet. Das ist insbesondere bei der Kombination PHP 5 + Windows nicht der Fall (auch bei 64-bit-Windows nicht). Bei PHP 7 klappt es aber, ebenso wie bei PHP 5 unter 64-bit-Linux.

eauth commented 7 years ago

Dieser Code funktioniert für (betragsmäßig) größere negative Zahlen nicht. Mit z.B. $turnover = -1000000000000 kommt nach Hin- und Rückwärtskonvertierung $umsatz = +99511627776 heraus (auch auf Systemen, auf denen PHP 64-Bit-Integer unterstützt).

Der Code funktioniert nur mit 5-Byte (40-Bit), mehr wird ja wohl niemand für den Umsatzzähler brauchen. Das pack('N'..) habe ich bewusst anstelle von pack('J'..) gewählt weil das 'J' (64-Bit) erst ab php 5.6.3 eingeführt wurde.

Die 64-Bit Unterstützung ist natürlich Grundvoraussetzung, das sollte man mit PHP_INT_SIZE vorher überprüfen um spätere unliebsame Überraschungen zu vermeiden.

lebail commented 7 years ago

OK, dass das nur für 5 Byte ist, hatte ich überlesen. Dann passt es natürlich.

afill commented 7 years ago

Herzlichen Dank