آیا اگر چهار رقم وسط کد ملی را حذف کنیم، هویت فرد دارنده ی کد ملی پنهان می ماند؟ در واقع: با حذف این چهار رقم، کد ملی چه اعدادی می تواند باشد و آیا نمی توان با حدس زدن به کد ملی فرد دست یافت؟
در بخش آخر مطلب «کدام استان ها بیشتر ماشین برنده شدند؟ (تحلیل کدهای ملی برندگان خرید ماشین ایران خودرو» نوشتم که یکی از کارهای ممکن با کدهای ملی منتشرشده در این قرعه کشی، این است که حدس بزنیم چهار رقم حذف شده ی وسط این کدهای ملی چه اعدادی ممکن است باشد. این بخش را برای کسانی که مطلب قبلی را نخواندند تکرار می کنم:
یکی از کارهای دیگری که می شود با این کدهای ملی کرد، حدس کدهای ملی است. در این فهرست سه رقم اول و سه رقم آخر کد ملی منتشر شده بود و چهار رقم میانی مخفی شده بود. مثلن: 228****644. گفتم که در کد ملی های ایرانی سه رقم اول کد ملی مربوط به «کد محل تولد فرد» است و آخرین رقم «رقم کنترل» است. با محاسباتی از 9 رقم دیگر (به جز رقم آخر) و مقایسه با رقم آخر، می توان پی برد که کد ملی درست است یا نه (توضیحات بیشتر در این جا). کاری که برنامه های مختلف (مثل این جا) برای بررسی صحت کد ملی انجام می دهند. در واقع این برنامه ها پایگاه داده ای از کدهای ملی ندارند. مثلن در این جا، این کد با php نوشته شده است. یکی از مثال های مرسوم فریب دادن این برنامه ها، وارد کردن عدد «1111111111» (ده یک) است. قاعدتن چنین کد ملی ای وجود ندارد اما این برنامه ها صحت این کد را تایید می کنند.
حالا یکی از کارهایی که با چنین مجموعه داده ای می توان انجام داد این است که با توجه به 6 رقم موجود، حدس بزنیم که 4 رقم پنهان شده، چند حالت معتبر (از 10 هزار حالت ممکن) دارد و چه عددهایی می تواند باشد. به این ترتیب می توان فهمید که آیا پنهان کردن این تعداد رقم، برای مخفی ماندن هویت افراد کافی است یا نه.
برای بررسی این مسئله، دوباره به همان داده های منتشرشده برمی گردیم. من فقط کدهای ملی را در یک فایل جدا قرار دادم.
طبق الگوریتم کد ملی، یک کد ملی معتبر باید با انجام کارهای زیر به نتایج معتبر برسد:
(2 * 10) + (2 * 9) + (8 * 8) + (0 * 7) + (0 * 6) + (7 * 5) + (5 * 4) + (6 * 3) + (4 * 2) = 183
پس کد این بخش (در حالت عادی) چنین چیزی می شود (در php):
$count_all_digits = array_sum(str_split((string)$number)); $remainder = $count_all_digits ; if (($remainder < 2 && $control_digit == $remainder) || $control_digit == 11 - $remainder) { echo "true" }
در خط اول عددمان را ابتدا به string تبدیل کردیم تا بتوانیم با str_Split به آرایه تبدیلش کنیم (یعنی هر رقم تبدیل به یکی از خانه های آرایه شود) تا بتوانیم با array_sum ارقام عددمان را با هم جمع کنیم. در خط بعدی باقیمانده حاصل جمع ارقام بر 11 را حساب کردیم و در خط سوم تا پنجم، شروطمان را بررسی کردیم و اگر شروط برقرار بودند (عدد ما، یک کد ملی معتبر بود)، true را چاپ کرده ایم.
حالا ما 4 رقم پنهان شده داریم. در واقع 3 رقم اول و 3 رقم آخر را داریم و 4 رقم وسط را نداریم. می خواهیم ببینیم این ارقام پنهان شده چه رقم هایی می توانند باشند که عدد ما یک کد ملی معتبر محسوب شود.
راه ساده این است که از 0001 تا 9999 را بررسی کنیم و برای هر یک از این 10 هزار حالت، الگوریتم کد ملی را اجرا کنیم.
یک کلاس ساده نوشته ام که قرار است محاسبات مربوط به معتبر بودن کد ملی را انجام دهد:
class NationalCode { private $number_we_have; private $control_digit; function __construct ($number) { $number_array = str_split((string)$number); $this->number_we_have = $number_array[0] * 10 + $number_array[1] * 9 + $number_array[2] * 8 + $number_array[7] * 3 + $number_array[8] * 2; $this->control_digit = $number_array[9]; } public function count_valid_numbers() { $count_valid_numbers = 0; for ($i = 0; $i < 10000; $i++) { $count_all_digits = $this->sum_four_middle_digits($i) + $this->number_we_have; $remainder = $count_all_digits ; if (($remainder < 2 && $this->control_digit == $remainder) || $this->control_digit == 11 - $remainder) { $count_valid_numbers++; } } return $count_valid_numbers; } private function sum_four_middle_digits($number) { if ($number < 10) { $sum = $number * 4; } elseif ($number < 100) { $number_array = str_split((string)$number); $sum = ($number_array[0] * 5) + ($number_array[1] * 4); } elseif ($number < 1000) { $number_array = str_split((string)$number); $sum = ($number_array[0] * 6) + ($number_array[1] * 5) + ($number_array[2] * 4); } else { $number_array = str_split((string)$number); $sum = ($number_array[0] * 7) + ($number_array[1] * 6) + ($number_array[2] * 5) + ($number_array[3] * 4); } return $sum; } }
در تابع سازنده ی این کلاس جمع ارقامی را که آشکار هستند و رقم کنترل را حساب می کنیم. یک تابع هم برای جمع چهار رقم وسط می سازیم و در نهایت یک تابع که کدهای ملی معتبر را بشمارد و به ما بگوید. حالا کافی است این کد را برای آن فایل csv که اول کار ساختیم اجرا کنیم:
ini_set('max_execution_time', 3000); $codes_file = fopen("national-codes.csv", "r"); while ( ($data_line = fgetcsv($codes_file)) !== FALSE) { $number = new NationalCode($data_line[0]); echo $number->count_valid_numbers()."<br>" }
در خط اول مدت اجرا را روی 30 دقیقه می گذارم تا برای 10 هزار بار حلقه روی 15000 تا عدد کم نیاورد! در خطوط بعدی هم به ازای هر خط از فایل، یک نمونه از کلاس می سازیم و تعداد کدهای ملی ممکن را محاسبه می کنیم (کل کد در همان مخزن قبلی هست).
خب نتیجه ی اجرای این برنامه این بود که تعداد کدهای ملی ممکن برای هر عدد ورودی یکی از اعداد زیر بود:
پس می شود نتیجه گرفت که پنهان کردن 4 رقم برای پنهان کردن هویت فرد کافی است.
کارهای دیگری که می شود کرد: