Luhn 算法(又稱 Mod-10)是 ISO/IEC 7812-1 標準,廣泛用於信用卡號、IMEI、加拿大社會保險號等識別碼的校驗位驗證。它能檢測單位數字錯誤、相鄰數字交換等常見輸入錯誤,但非加密算法,不可用於安全防護。
<?php
/**
* Luhn 算法驗證
* @param string $number 僅包含數字的字串
* @return bool true=校驗通過
*/
function luhnCheck(string $number): bool
{
// 1. 基礎檢查
if (!ctype_digit($number)) {
return false; // 含非數字字元
}
$len = strlen($number);
if ($len < 2) {
return false; // 至少需 1 位數字 + 1 位校驗位
}
$sum = 0;
$double = false; // 從右向左,校驗位不加倍
// 2. 從右向左遍歷
for ($i = $len - 1; $i >= 0; $i--) {
$digit = (int)$number[$i];
if ($double) {
$digit *= 2;
if ($digit > 9) {
$digit -= 9; // 等價於 $digit = $digit - 9 或 $digit = floor($digit/10) + $digit%10
}
}
$sum += $digit;
$double = !$double; // 翻轉標誌
}
// 3. 模 10 檢查
return ($sum % 10) === 0;
}
/** 生成校驗位(給定前綴) */
function luhnGenerateCheckDigit(string $prefix): int
{
// 在末尾加 0 計算總和
$sum = 0;
$double = true; // 因為會加上校驗位,所以從右數第二位開始加倍
for ($i = strlen($prefix) - 1; $i >= 0; $i--) {
$digit = (int)$prefix[$i];
if ($double) {
$digit *= 2;
if ($digit > 9) $digit -= 9;
}
$sum += $digit;
$double = !$double;
}
$checkDigit = (10 - ($sum % 10)) % 10;
return $checkDigit;
}
<?php
$testCases = [
// 有效卡號(來自維基百科/標準測試向量)
'4532015112830366' => true, // Visa
'5555555555554444' => true, // Mastercard
'378282246310005' => true, // Amex
'6011111111111117' => true, // Discover
'3566002020360505' => true, // JCB
// 無效:修改最後一位
'4532015112830367' => false,
// 無效:非數字
'4532-0151-1283-0366' => false,
// 無效:長度不足
'123' => false,
];
foreach ($testCases as $num => $expected) {
$result = luhnCheck($num);
$status = $result === $expected ? '✅' : '❌';
echo "$status [$num] => " . ($result ? 'Valid' : 'Invalid') . " (期望: " . ($expected ? 'Valid' : 'Invalid') . ")\n";
}
// 生成校驗位演示
$prefix = '453201511283036'; // 缺最後一位
$checkDigit = luhnGenerateCheckDigit($prefix);
echo "前綴: $prefix -> 校驗位: $checkDigit -> 完整卡號: {$prefix}{$checkDigit}\n";
echo "驗證完整卡號: " . (luhnCheck("{$prefix}{$checkDigit}") ? '通過' : '失敗') . "\n";
雖然 PHP 後端必須驗證,但前端即時反饋能大幅提升 UX:
// JavaScript 版(ES6)
const luhnCheck = (num) => {
const digits = num.replace(/\D/g, '').split('').map(Number);
if (digits.length < 2) return false;
let sum = 0, double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits[i];
if (double) { d *= 2; if (d > 9) d -= 9; }
sum += d;
double = !double;
}
return sum % 10 === 0;
};
| 誤區 | 正確認知 |
|---|---|
| Luhn 能防止偽造卡號 | 只能檢測隨機輸入錯誤,攻擊者極易構造合法 Luhn 卡號(只需調整最後一位)。 |
| 所有 16 位數字卡號都適用 | 不同卡組織長度不同:Visa/MC 16 位,Amex 15 位,銀聯 16-19 位。驗證前應先識別 BIN 段。 |
| 可用於密碼強度檢查 | 完全無關,Luhn 僅針對數字串校驗位。 |
Luhn 算法實現簡單、零依賴,是表單驗證、卡號掃描、設備識別碼校驗的首選。記住:後端必驗、前端輔助、不可信任、非加密。將 luhnCheck 封裝為獨立 Composer 包或工具類,便於多專案複用與單元測試覆蓋。