Converting a Visual Basic function to PHP - php
I have a Visual Basic function and I am not that familiar with VB. I need to convert it to PHP and have made a start. There are a couple of functions I'm not sure how to replicate and am looking for some help with this and to see if I have got the nesting right etc. In the following code, there is the vb function and then my attempt at te php version. it is not complete and in the php version I have commented out the vb parts I am not sure about. Can anyone help put me on the right lines?
Public Function gfnCrypt(ByVal Expression As String, ByVal Password As String) As String
'RC4 Encryption
Dim rb(0 To 255) As Integer, X As Long, Y As Long, Z As Long, Key() As Byte, ByteArray() As Byte, temp As Byte
On Error Resume Next
If Len(Password) = 0 Then
Exit Function
End If
If Len(Expression) = 0 Then
Exit Function
End If
If Len(Password) > 256 Then
Key() = StrConv(Left$(Password, 256), vbFromUnicode)
Else
Key() = StrConv(Password, vbFromUnicode)
End If
For X = 0 To 255
rb(X) = X
Next X
X = 0
Y = 0
Z = 0
For X = 0 To 255
Y = (Y + rb(X) + Key(X Mod Len(Password))) Mod 256
temp = rb(X)
rb(X) = rb(Y)
rb(Y) = temp
Next X
X = 0
Y = 0
Z = 0
ByteArray() = StrConv(Expression, vbFromUnicode)
For X = 0 To Len(Expression)
Y = (Y + 1) Mod 256
Z = (Z + rb(Y)) Mod 256
temp = rb(Y)
rb(Y) = rb(Z)
rb(Z) = temp
ByteArray(X) = ByteArray(X) Xor (rb((rb(Y) + rb(Z)) Mod 256))
Next X
gfnCrypt = StrConv(ByteArray, vbUnicode)
End Function
And in PHP:
function gfnCrypt($mywebpassword, $mywebkey) {
//'RC4 Encryption
//Dim rb(0 To 255) As Integer, X As Long, Y As Long, Z As Long, Key() As Byte, ByteArray() As Byte, temp As Byte
if(strlen($mywebpassword) == 0){
return false;
}
if(strlen($mywebkey) == 0){
return false;
}
if(strlen($mywebpassword) > 256){
//Key() = StrConv(Left$(Password, 256), vbFromUnicode)
}else{
//Key() = StrConv(Password, vbFromUnicode)
}
$rb=array();
for($x=0;$x=255;$x++){
$rb['x'] = $x;
for($x=0;$x=255;$x++){
$y = ($y + $rb['x'];// + Key(X Mod Len(Password))) Mod 256
$temp = $rb['x'];
$rb['x'] = $rb[$y];
$rb[$y] = $temp;
//ByteArray() = StrConv(Expression, vbFromUnicode)
for($x=0;$x=strlen($mywebpassword), $x++){
$y = ($y + 1);// Mod 256
$z = ($z + $rb[$y]);// Mod 256
$temp = $rb[$y];
$rb[$y] = $rb[$z];
$rb[$z] = $temp;
//ByteArray(X) = ByteArray(X) Xor (rb((rb(Y) + rb(Z)) Mod 256))
}
}
}
//gfnCrypt = StrConv(ByteArray, vbUnicode)
return $gfnCrypt;
}
The arithmetic modulo can be used in php using % operator.
$modulus = $a % $b
I think you will get errors with your 3 embedded for (x…) loops by the way.
To access an element of the array (the $ith for instance) don't use $array['i'] but $array[$i].
The StrConv must be a ut8_encode and the left stuff can be done with substr($string, 0, 255).
References: utf8_encode substr
If you are trying to do RC4 encryption in PHP then you may wan't to take a look at the project at this link http://code.google.com/p/rc4crypt/
I really hope you have some unit tests to test that:
function gfnCrypt($mywebpassword, $mywebkey) {
//'RC4 Encryption
//Dim rb(0 To 255) As Integer, X As Long, Y As Long, Z As Long, Key() As Byte, ByteArray() As Byte, temp As Byte
rb = array();
Key = array();
ByteArray = array();
if(strlen($mywebpassword) == 0){
return false;
}
if(strlen($mywebkey) == 0){
return false;
}
if(strlen($mywebpassword) > 256){
//Key() = StrConv(Left$(Password, 256), vbFromUnicode)
Key[] = ut8_encodesubstr(Password, 0, 256));
}else{
//Key() = StrConv(Password, vbFromUnicode)
Key[] = ut8_encode(Password);
}
$rb=array();
for($x=0;$x=255;$x++){
$rb['x'] = $x;
for($x=0;$x=255;$x++){
$y = ($y + $rb['x'] + Key(X % strlen(Password))) % 256;
$temp = $rb['x'];
$rb['x'] = $rb[$y];
$rb[$y] = $temp;
//ByteArray() = StrConv(Expression, vbFromUnicode)
ByteArray[] = ut8_encode(Expression);
for($x=0;$x=strlen($mywebpassword), $x++){
$y = ($y + 1);// Mod 256
$z = ($z + $rb[$y]);// Mod 256
$temp = $rb[$y];
$rb[$y] = $rb[$z];
$rb[$z] = $temp;
//ByteArray(X) = ByteArray(X) Xor (rb((rb(Y) + rb(Z)) Mod 256))
ByteArray[X] = (ByteArray[X] ^= (rb[(rb[Y] + rb[Z]] % 256)]);
}
}
}
//gfnCrypt = StrConv(ByteArray, vbUnicode)
gfnCrypt = ut8_encode(ByteArray);
return $gfnCrypt;
}
I finished the code you started, but it seems really wrong (for example, why nest 3 for loops, using the same variable ?). It doesn't even seem to match the initial VB code...
Related
Convert VB6 code to PHP
I want to convert Visual Basic 6 Code to PHP Code. I am new to PHP please help me to convert my VB6 Code to PHP. So far I tried to convert this into php code when I tried the code there is an error in the " CryptRC4 = CryptRC4 & Chr$((pvCryptXor(baS((CLng(baS(li)) + baS(lJ)) Mod 256), Asc(Mid$(sText, lIdx, 1))))); part and also I don't know how to proceed to the sub functions. Please see the codes below. The vb code is used to encrypt strings. I want to convert it to php format. PHP Code <?php function CryptRC4($sText,$sKey){ $baS = array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255); $baK = array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255); $bytSwap = 0; $li = 0; $lJ = 0; $lIdx = 0; for( $lIdx = 0; $lIdx < 256; $lIdx++){ $baS[$lIdx] = $lIdx; $baK[$lIdx] = ord(substr($sKey, 1 + ($lIdx % strlen($sKey)), 1)); } for($li = 0; $li < 256; $li++){ $lJ = ($baS[$li] + $baK[$li]) % 256; $bytSwap = $baS[$li]; $baS[$li] = $baS[$lJ]; $baS[$lJ] = $bytSwap; } $li = 0; $lJ = 0; $data_str = ""; for($lIdx = 0; $lIdx < strlen($sText); $lIdx++){ $li = ($li + 1) % 256; $lJ = ($lJ + $baS[$li]) % 256; $bytSwap = $baS[$li]; $baS[$li] = $baS[$lJ]; $baS[$lJ] = $bytSwap; #echo chr((pvCryptXor($baS[(round(($baS[$li]) + $baS[$lJ])) % 256], ord(substr($sText, $lIdx, 1))))); $data_str .= chr((pvCryptXor($baS[(round(($baS[$li]) + $baS[$lJ])) % 256], ord(substr($sText, $lIdx, 1))))); } echo $data_str; } function pvCryptXor($li, $lJ){ if($li = $lJ){ $pcx = $lJ; } else { $pcx = $li Xor $lJ; } return $pcx; } unction ToHexDump($sText) { $lIdx; for($lIdx = 1; $lIdx < strlen($sText); $lIdx++){ $thd .= Right$("0" & Hex(Asc(Mid(sText, lIdx, 1))), 2) echo $thd; } return $thd; } FromHexDump("events"); function FromHexDump($sText) { $fhd = ""; for($lIdx = 0; $lIdx < strlen($sText); $lIdx++){ $fhd .= chr(CLng("&H" & Mid(sText, lIdx, 2))); } return $fhd; } ?> VB Code: Public Function CryptRC4(sText As String, sKey As String) As String On Error Resume Next Dim baS(0 To 255) As Byte Dim baK(0 To 255) As Byte Dim bytSwap As Byte Dim li As Long Dim lJ As Long Dim lIdx As Long For lIdx = 0 To 255 baS(lIdx) = lIdx baK(lIdx) = Asc(Mid$(sKey, 1 + (lIdx Mod Len(sKey)), 1)) Next For li = 0 To 255 lJ = (lJ + baS(li) + baK(li)) Mod 256 bytSwap = baS(li) baS(li) = baS(lJ) baS(lJ) = bytSwap Next li = 0 lJ = 0 For lIdx = 1 To Len(sText) li = (li + 1) Mod 256 lJ = (lJ + baS(li)) Mod 256 bytSwap = baS(li) baS(li) = baS(lJ) baS(lJ) = bytSwap CryptRC4 = CryptRC4 & Chr$((pvCryptXor(baS((CLng(baS(li)) + baS(lJ)) Mod 256), Asc(Mid$(sText, lIdx, 1))))) Next End Function Private Function pvCryptXor(ByVal li As Long, ByVal lJ As Long) As Long On Error Resume Next If li = lJ Then pvCryptXor = lJ Else pvCryptXor = li Xor lJ End If End Function Public Function ToHexDump(sText As String) As String On Error Resume Next Dim lIdx As Long For lIdx = 1 To Len(sText) ToHexDump = ToHexDump & Right$("0" & Hex(Asc(Mid(sText, lIdx, 1))), 2) Next End Function Public Function FromHexDump(sText As String) As String On Error Resume Next Dim lIdx As Long For lIdx = 1 To Len(sText) Step 2 FromHexDump = FromHexDump & Chr$(CLng("&H" & Mid(sText, lIdx, 2))) Next End Function
I revised your updated code and it seems you only had a few minor errors in it, look and my changes: I guess you can use the build in PHP function hex2bin and bin2hex instead fo you own hex conversion. function CryptRC4($sText,$sKey){ $baS = range(0, 255); // you can use range instead of your manual arrays $baK = range(0, 255); $bytSwap = 0; $li = 0; $lJ = 0; $lIdx = 0; for( $lIdx = 0; $lIdx < 256; $lIdx++){ $baS[$lIdx] = $lIdx; $baK[$lIdx] = ord(substr($sKey, 1 + ($lIdx % strlen($sKey)), 1)); } for($li = 0; $li < 256; $li++){ $lJ = ($baS[$li] + $baK[$li]) % 256; $bytSwap = $baS[$li]; $baS[$li] = $baS[$lJ]; $baS[$lJ] = $bytSwap; } $li = 0; $lJ = 0; $data_str = ""; for($lIdx = 0; $lIdx < strlen($sText); $lIdx++){ $li = ($li + 1) % 256; $lJ = ($lJ + $baS[$li]) % 256; $bytSwap = $baS[$li]; $baS[$li] = $baS[$lJ]; $baS[$lJ] = $bytSwap; #echo chr((pvCryptXor($baS[(round(($baS[$li]) + $baS[$lJ])) % 256], ord(substr($sText, $lIdx, 1))))); $data_str .= chr((pvCryptXor($baS[(round(($baS[$li]) + $baS[$lJ])) % 256], ord(substr($sText, $lIdx, 1))))); } return $data_str; // changed from echo to return } function pvCryptXor($li, $lJ){ if($li == $lJ){ // you had an error here, use == to compare instead of a single = $pcx = $lJ; } else { $pcx = $li ^ $lJ; // XOR function in PHP is the ^ operator } return $pcx; } $str_hex = bin2hex("events"); $str_enc = CryptRC4($str_hex,"password"); $str_dec = hex2bin(CryptRC4($str_enc,"password")); echo $str_hex . PHP_EOL . $str_enc . PHP_EOL . $str_dec; OUTPUT: 6576656e7473 '�����~i�� events So it seems to me as it's actually encoding and decoding correctly!?
It seems the original VB6 implementation of CryptRC4 function is from my answer to "VB6 encrypt text using password" question on SO. So let me try answering your Q with this short php implementation of all public functions in the VB6 snippet: function CryptRC4($text, $key) { return openssl_encrypt($text, "RC4-40", $key, 1 | 2); } function ToHexDump($text) { return strtoupper(bin2hex($text)); } function FromHexDump($text) { return hex2bin($text); } You can excercise these one-liners with something like this: $text = "a message here"; $password = "password"; $encr = ToHexDump(CryptRC4($text, $password)); $decr = CryptRC4(FromHexDump($encr), $password); echo $text . PHP_EOL . $encr . PHP_EOL . $decr;
As it's intended for passwords, you can save yourself a lot of hassle. PHP has got built in functions (version 5.5 and newer) that are designed for dealing with the hashing of passwords and for verifying hashed passwords against the password submitted by a user. Have a read through the PHP relevant PHP manual pages http://php.net/manual/en/book.password.php
php bit array in integer
I have written a wrapper class around a byte stream in order to read bit by bit from that stream (bit arrays) using this method: public function readBits($len) { if($len === 0) { return 0; } if($this->nextbyte === null) { //no byte has been started yet if($len % 8 == 0) { //don't start a byte with the cache, even number of bytes $ret = 0; //just return byte count not bit count $len /= 8; while ($len--) { if($this->bytestream->eof()) { //no more bytes return false; } $byte = $this->bytestream->readByte(); $ret = ($ret << 8) | ord($byte); } return $ret; } else { $this->nextbyte = ord($this->bytestream->readByte()); $this->byteshift = 0; } } if($len <= 8 && $this->byteshift + $len <= 8) { //get the bitmask e.g. 00000111 for 3 $bitmask = self::$includeBitmask[$len - 1]; //can be satisfied with the remaining bits $ret = $this->nextbyte & $bitmask; //shift by len $this->nextbyte >>= $len; $this->byteshift += $len; } else { //read the remaining bits first $bitsremaining = 8 - $this->byteshift; $ret = $this->readBits($bitsremaining); //decrease len by the amount bits remaining $len -= $bitsremaining; //set the internal byte cache to null $this->nextbyte = null; if($len > 8) { //read entire bytes as far as possible for ($i = intval($len / 8); $i > 0; $i--) { if($this->bytestream->eof()) { //no more bytes return false; } $byte = $this->bytestream->readByte(); $ret = ($ret << 8) | ord($byte); } //reduce len to the rest of the requested number $len = $len % 8; } //read a new byte to get the rest required $newbyte = $this->readBits($len); $ret = ($ret << $len) | $newbyte; } if($this->byteshift === 8) { //delete the cached byte $this->nextbyte = null; } return $ret; } This allows me to read bit arrays of arbitrary length off my byte stream which are returned in integers (as php has no signed integers). The problem appears once I try to read a bit array that is bigger than 64 bits and I am assuming if I were to use the class on a 32 bit system the problem would appear with 32 bit arrays already. The problem is that the return value is obviously to big to be held within an integer, so it topples over into a negative integer. My question now is what would be the best way to deal with this. I can think of: Forcing the number to be saved as a string (I am unsure if that's even possible) Use the GMP extension (which I kinda don't want to because I think the gmp bitwise methods are probably quite a performance hit compared to the normal bitwise operators) Is there something I missed on this or is one of the options I mentioned actually the best way to deal with this problem? Thanks for your help in advance
How can I get the offsets of all set bits in a bitarray?
In C, Ruby or PHP, how can I get the offsets of all set bits in an bitarray. E.g.: Bitarray: 1 0 0 1 0 Offset: 5 4 3 2 1 Yields 2 and 5. 10001 => {1,5} 11 => {1,2} 1001001 => {1,4,7} The most obvious solution would be to first do a reversed Find first set to know the length and then loop through the bits, saving the offset/index. However this does not seem very smart. Something like FFSR multiple times with subtraction might be better.
I came up with this. I've used binary shift operation to find out if there is binary "1" or "0". Probably you could use this code as your starting point. #include <stdio.h> int main() { int number; int counter = 1; printf("Please input the number to get the offsets: "); scanf("%d", &number); printf("The required positions of ones: "); while ((number != 0)) { if ((number % 2) != 0) { number = number >> 1; printf("%d", counter); } else { number = number >> 1; } counter++; } return 0; } Here is an extended version that prints binary representation as well: #include <stdio.h> #include <strings.h> char* rev(char* str); int main() { int number, temp; int counter = 1; char str[32] = ""; printf("Please input the number to get the offsets: "); scanf("%d", &number); temp = number; printf("Binary representation: "); while ((temp != 0)) { if ((temp % 2) != 0) { temp = temp >> 1; strncat(str, "1", 1); } else { temp = temp >> 1; strncat(str, "0", 1); } } printf("%s", rev(str)); printf("\nThe required positions of ones: "); while ((number != 0)) { if ((number % 2) != 0) { number = number >> 1; printf("%d", counter); } else { number = number >> 1; } counter++; } getch(); getch(); return 0; } char* rev(char* str) { int end= strlen(str) - 1; int start = 0; while( start<end ) { str[start] ^= str[end]; str[end] ^= str[start]; str[start]^= str[end]; ++start; --end; } return str; }
PHP implementation for an URL shortening algorithm
I found Marcel Jackwerth's response to How to code a URL shortener? to be a good answer for the problem, however my question is how it'll look in PHP? Here's Marcel's answer: You need a Bijective Function f (there must be no x1 != x2, that will make f(x1) = f(x2); and for every y you will find a x so that f(x)=y). This is necessary so that you can find a inverse function g('abc') = 123 for your f(123)='abc' function. I would continue your "convert number to string" approach (however you will realize that your proposed algorithm fails if your id is a prime and greater than 52). How to convert the id to a shortened url: Think of an alphabet you want to use. In your case that's [a-zA-Z0-9]. It contains 62 letters. Take the auto-generated unique numerical key (auto-incremented id): for example 125 (a decimal number) Now you have to convert the 125 (base 10) to X (base 62). This will then be {2}{1} (2×62+1=125). Now map the symbols {2} and {1} to your alphabet. Say {0} = 'a', {25} = 'z' and so on. We will have {2} = 'c' and {1} = 'b'. So '/cb' will be your shortened url. How to resolve a shortened url abc to the initial id: If you want to do this in reverse, it's not quite diffcult. 'e9a' will be resolved to "4th,61st,0th letter in alphabet" = {4}{61}{0}, which is 4×62×62 + 61×62 + 0 = 19158. You will then just have to find your database-record with id 19158.
function convert($src, $srcAlphabet, $dstAlphabet) { $srcBase = strlen($srcAlphabet); $dstBase = strlen($dstAlphabet); $wet = $src; $val = 0; $mlt = 1; while ($l = strlen($wet)) { $digit = $wet[$l - 1]; $val += $mlt * strpos($srcAlphabet, $digit); $wet = substr($wet, 0, $l - 1); $mlt *= $srcBase; } $wet = $val; $dst = ''; while ($wet >= $dstBase) { $digitVal = $wet % $dstBase; $digit = $dstAlphabet[$digitVal]; $dst = $digit . $dst; $wet /= $dstBase; } $digit = $dstAlphabet[$wet]; $dst = $digit . $dst; return $dst; } // prints cb print convert('125', '0123456789', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); // prints 19158 print convert('e9a', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '0123456789');
I like this PHP function which allows you to customise the alphabet (and remove confusing 0/O's etc.) // From http://snipplr.com/view/22246/base62-encode--decode/ private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') { $str = ''; do { $i = fmod($val, $base); $str = $chars[$i] . $str; $val = ($val - $i) / $base; } while($val > 0); return $str; } Follow the URL to find the reverse 'decode' function too.
The main problem with Marcel's solution is that it uses a zero digit as a placeholder. By converting between bases, inevitably the numeral chosen to represent 0 can't appear at the front of the converted number. For example, if you convert base 10 integers to base 4 using "ABCD" using the provided mechanism, there is no way to obtain output that starts with the letter "A", since that represents a zero in the new base and won't prefix the number. You might expect 5 to be "AA", but instead, it is "BA". There is no way to coerce that algorithm into producing "AA", because it would be like writing "00" in decimal, which has the same value as "0". Here's an alternate solution in PHP that uses the entire gamut: function encode($n, $alphabet = 'ABCD') { $output = ''; if($n == 0) { $output = $alphabet[0]; } else { $digits = floor(log($n, strlen($alphabet))) + 1; for($z = 0; $z < $digits; $z++) { $digit = $n % 4; $output = $alphabet[$digit] . $output; $n = floor($n / 4) - 1; } } return $output; } function decode($code, $alphabet = 'ABCD') { $n = 0; $code = str_split($code); $unit = 1; while($letter = array_pop($code)) { $n += (strpos($alphabet, $letter) + 1) * $unit; $unit = $unit * strlen($alphabet); } return $n - 1; } echo encode(25); // should output "ABB" echo decode('ABB'); // should output 25 Change/pass the second parameter to a list of characters to use instead of the short 4-character dictionary of "ABCD".
all you need to do is convert between different base systems base 10 to base 62 https://github.com/infinitas/infinitas/blob/dev/core/short_urls/models/short_url.php
Generating Luhn Checksums
There are lots of implementations for validating Luhn checksums but very few for generating them. I've come across this one however in my tests it has revealed to be buggy and I don't understand the logic behind the delta variable. I've made this function that supposedly should generated Luhn checksums but for some reason that I haven't yet understood the generated checksums are invalid half of the time. function Luhn($number, $iterations = 1) { while ($iterations-- >= 1) { $stack = 0; $parity = strlen($number) % 2; $number = str_split($number, 1); foreach ($number as $key => $value) { if ($key % 2 == $parity) { $value *= 2; if ($value > 9) { $value -= 9; } } $stack += $value; } $stack = 10 - $stack % 10; if ($stack == 10) { $stack = 0; } $number[] = $stack; } return implode('', $number); } Some examples: Luhn(3); // 37, invalid Luhn(37); // 372, valid Luhn(372); // 3728, invalid Luhn(3728); // 37283, valid Luhn(37283); // 372837, invalid Luhn(372837); // 3728375, valid I'm validating the generated checksums against this page, what am I doing wrong here? For future reference, here is the working function. function Luhn($number, $iterations = 1) { while ($iterations-- >= 1) { $stack = 0; $number = str_split(strrev($number), 1); foreach ($number as $key => $value) { if ($key % 2 == 0) { $value = array_sum(str_split($value * 2, 1)); } $stack += $value; } $stack %= 10; if ($stack != 0) { $stack -= 10; } $number = implode('', array_reverse($number)) . abs($stack); } return $number; } I dropped the $parity variable since we don't need it for this purpose, and to verify: function Luhn_Verify($number, $iterations = 1) { $result = substr($number, 0, - $iterations); if (Luhn($result, $iterations) == $number) { return $result; } return false; }
Edit: Sorry, I realize now that you had almost my entire answer already, you had just incorrectly determined which factor to use for which digit. My entire answer now can be summed up with this single sentence: You have the factor reversed, you're multiplying the wrong digits by 2 depending on the length of the number. Take a look at the Wikipedia article on the Luhn algorithm. The reason your checksum is invalid half the time is that with your checks, half the time your number has an odd number of digits, and then you double the wrong digit. For 37283, when counting from the right, you get this sequence of numbers: 3 * 1 = 3 3 8 * 2 = 16 --> 1 + 6 = 7 2 * 1 = 2 2 7 * 2 = 14 --> 1 + 4 = 5 + 3 * 1 = 3 3 = 20 The algorithm requires you to sum the individual digits from the original number, and the individual digits of the product of those "every two digits from the right". So from the right, you sum 3 + (1 + 6) + 2 + (1 + 4) + 3, which gives you 20. If the number you end up with ends with a zero, which 20 does, the number is valid. Now, your question hints at you wanting to know how to generate the checksum, well, that's easy, do the following: Tack on an extra zero, so your number goes from xyxyxyxy to xyxyxyxy0 Calculate the luhn checksum sum for the new number Take the sum, modulus 10, so you get a single digit from 0 to 10 If the digit is 0, then congratulations, your checksum digit was a zero Otherwise, calculate 10-digit to get what you need for the last digit, instead of that zero Example: Number is 12345 Tack on a zero: 123450 Calculate the luhn checksum for 123450, which results in 0 5 4 3 2 1 1 2 1 2 1 2 <-- factor 0 10 4 6 2 2 <-- product 0 1 0 4 6 2 2 <-- sum these to: 0+1+0+4+6+2+2=15 Take the sum (15), modulus 10, which gives you 5 Digit (5), is not zero Calculate 10-5, which gives you 5, the last digit should be 5. So the result is 123455.
your php is buggy, it leads into an infinite loop. This is the working version that I'm using, modified from your code function Luhn($number) { $stack = 0; $number = str_split(strrev($number)); foreach ($number as $key => $value) { if ($key % 2 == 0) { $value = array_sum(str_split($value * 2)); } $stack += $value; } $stack %= 10; if ($stack != 0) { $stack -= 10; $stack = abs($stack); } $number = implode('', array_reverse($number)); $number = $number . strval($stack); return $number; } Create a php and run in your localhost Luhn(xxxxxxxx) to confirm.
BAD I literally cannot believe how many crummy implementations there are out there. IDAutomation has a .NET assembly with a MOD10() function to create but it just doesn't seem to work. In Reflector the code is way too long for what it's supposed to be doing anyway. BAD This mess of a page which is actually currently linked to from Wikipedia(!) for Javascript has several verification implementations that don't even return the same value when I call each one. GOOD The page linked to from Wikipedia's Luhn page has a Javascript encoder which seems to work : // Javascript String.prototype.luhnGet = function() { var luhnArr = [[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8,1,3,5,7,9]], sum = 0; this.replace(/\D+/g,"").replace(/[\d]/g, function(c, p, o){ sum += luhnArr[ (o.length-p)&1 ][ parseInt(c,10) ] }); return this + ((10 - sum%10)%10); }; alert("54511187504546384725".luhnGet()); GOOD This very useful EE4253 page verifies the check-digit and also shows the full calculation and explanation. GOOD I needed C# code and ended up using this code project code: // C# public static int GetMod10Digit(string data) { int sum = 0; bool odd = true; for (int i = data.Length - 1; i >= 0; i--) { if (odd == true) { int tSum = Convert.ToInt32(data[i].ToString()) * 2; if (tSum >= 10) { string tData = tSum.ToString(); tSum = Convert.ToInt32(tData[0].ToString()) + Convert.ToInt32(tData[1].ToString()); } sum += tSum; } else sum += Convert.ToInt32(data[i].ToString()); odd = !odd; } int result = (((sum / 10) + 1) * 10) - sum; return result % 10; } GOOD This validation code in C# seems to work, if a little unwieldy. I just used it to check the above was correct.
There's now a github repo based on the original question/answer. See https://github.com/xi-project/xi-algorithm It's also available at packagist
This is a function that could help you, it's short and it works just fine. function isLuhnValid($number) { if (empty($number)) return false; $_j = 0; $_base = str_split($number); $_sum = array_pop($_base); while (($_actual = array_pop($_base)) !== null) { if ($_j % 2 == 0) { $_actual *= 2; if ($_actual > 9) $_actual -= 9; } $_j++; $_sum += $_actual; } return $_sum % 10 === 0; }
Since the other answers that displayed or linked to C# weren't working, I've added a tested and more explanatory C# version: /// <summary> /// Calculates Luhn Check Digit based on /// https://en.wikipedia.org/wiki/Luhn_algorithm /// </summary> /// <param name="digits">The digits EXCLUDING the check digit on the end. /// The check digit should be compared against the result of this method. /// </param> /// <returns>The correct checkDigit</returns> public static int CalculateLuhnCheckDigit(int[] digits) { int sum = 0; bool isMultiplyByTwo = false; //Start the summing going right to left for (int index = digits.Length-1; index >= 0; --index) { int digit = digits[index]; //Every other digit should be multipled by two. if (isMultiplyByTwo) digit *= 2; //When the digit becomes 2 digits (due to digit*2), //we add the two digits together. if (digit > 9) digit = digit.ToString() .Sum(character => (int)char.GetNumericValue(character)); sum += digit; isMultiplyByTwo = !isMultiplyByTwo; } int remainder = sum % 10; //If theres no remainder, the checkDigit is 0. int checkDigit = 0; //Otherwise, the checkDigit is the number that gets to the next 10 if (remainder != 0) checkDigit = 10 - (sum % 10); return checkDigit; } An example of its use: public static bool IsValid(string userValue) { //Get the check digit from the end of the value int checkDigit = (int)char.GetNumericValue(userValue[userValue.Length - 1]); //Remove the checkDigit for the luhn calculation userValue = userValue.Substring(0, userValue.Length - 1); int[] userValueDigits = userValue.Select(ch => (int)char.GetNumericValue(ch)) .ToArray(); int originalLuhnDigit = CalculateLuhnCheckDigit(userValueDigits); //If the user entered check digit matches the calcuated one, //the number is valid. return checkDigit == originalLuhnDigit; }
The parity check must start from the right. Try this: <?php function Luhn($digits) { $sum = 0; foreach (str_split(strrev($digits)) as $i => $digit) { $sum += ($i % 2 == 0) ? array_sum(str_split($digit * 2)) : $digit; } return $digits . (10 - ($sum % 10)) % 10; } Add Luhn checksum to $input $digits = Luhn($input); Verify a number with Luhn checksum in it: if ($digits == Luhn(substr($digits, 0, -1))) { // ... } Get the checksum number: $luhn_digit = substr(Luhn($digits), -1);
#include <iostream> #include <string> #include <sstream> using namespace std; int main() { int *LONT, n, TARF; int SEGVT = 0; int SEGVT2 = 0; string TARJETA; double VA; cout << "cuantos digitos tiene la tarjeta: " << endl; cin >> n; LONT = new int[n]; do { cout << "ingrese el # de la tarjeta: " << endl; cin >> TARJETA; VA = stod(TARJETA); } while (VA < 0); for (int POS = 0; POS < TARJETA.size(); POS++) { LONT[POS] = TARJETA[POS] - '0'; } for (int i = 0; i < n; i++) { if (i % 2 == 0) { LONT[i] = TARJETA[i] - '0'; LONT[i] = LONT[i] * 2; if (LONT[i] >= 10) { LONT[i] = LONT[i] - 9; } SEGVT2 = SEGVT2 + LONT[i]; } else { LONT[i] = TARJETA[i] - '0'; SEGVT = SEGVT + LONT[i]; } } TARF = SEGVT + SEGVT2; if (TARF % 10 == 0) { cout << SEGVT2 << SEGVT; cout << "El numero de tarjeta " << TARJETA << "; Es de una tarjeta valida (YA QUE SU MOD10 ES " << TARF << endl; } else { cout << SEGVT2 << SEGVT; cout << "El numero de tarjeta" << TARJETA << "; No es de una tarjeta valida (YA QUE SU MOD10 ES " << TARF << endl; } delete[] LONT; }