Программа, устойчивая к редактированию

Клиент заказывает Программисту Складыватель. Складыватель читает два целых числа и печатает их сумму. Программист пишет:

a, b = map(int, input().split())
print(a + b)

Клиент доволен, но он опасается, что программа могла исказиться при передаче. Например, если + случайно заменился на -, программа продолжить работать, но результаты будут неверные. Клиент может понять что программа напечатала число, целое число он узнает, но проверить результат не может. В случае замены знаков, Клиент будет получать неправильные результаты и не узнает об этом. А это плохо.

Искажением считается удаление одного символа, или добавление одного символа, или замена одного символа.

Клиент отличает целые числа от другого текста. Если программа напечатает не число, Клиент это поймёт. Нельзя допустить чтобы искажённая программа напечатала единственное целое число, которое не равно сумме аргументов.

Вопрос: можно ли написать Складыватель, устойчивый к искажению?

Вопрос *: Для какого максимального количества искажений k можно написать k-устойчивый Складыватель?

P.S. Если искажённый Складыватель падает с ошибкой, это не проблема. Клиент увидит что есть ошибка и попросит Программиста переслать Складыватель ещё раз. Опасны только те искажения, которые Клиент не может отличить от правильной работы Складывателя.

Десятые сутки

dofi4ka, 22 искажения

dofi4ka – абсолютный лидер!

Наиболее полное решение. Указана верхняя граница устройчивости программы на Питоне – 23 символа, приведён соответствующий пример. Построена программа, которую нельзя сломать изменив только 22 символа. Хотя это не доказано формально (потому что доказать это крайне сложно!), никто не смог привести контрпример.

Вторые сутки

dofi4ka, 22 искажения

dofi4ka обновил своё решение. Это явный лидер, который, кажется, не ломается за 22 изменения. Напомню, что 23 изменения ломают любую программу на Питоне. То есть, это оптимальное решение для Питона.

Если вы захотите состязаться с ним, рекомендую посмотреть как можно решить задачу без exec. Или решить её на другом языке.

eri, 2 изменения

eri обновил своё решение. Сейчас два изменения, но потенциал есть.

Итоги первой ночи

CrazyElf, 1 искажение

Ответ CrazyElf был первым. Была высказана неожиданная идея о том что Складывать может восстанавливаться после искажений. К сожалению, сам складыватель пока можно испортить одним искажением. Ждём улучшений.

Serge3leo, ? искажений

10 часов назад. Тоже затронута тема восстановения от ошибок. Код нуждается в доводке.

dofi4ka, 6 или 22 (?) искажения

10 часов назад. Код зашифрован в base85. Очень сильный ход! Было заявлено 22 (двадцать два) искажения. Проверка показала, что программу можно сломать шестью искажениями, что тоже очень не плохо. Это рекорд!

Приведена верхняя оценка устойчивости программы на Питоне: любую программу можно исказить добавив 23 символа.

Несколько минут назад dofi4ka обновил ответ. Снова заявлены 22 искажения! Требуется проверка.

Pak Uula, ? искажений

5 часов назад. Поднята тема самокоррекции программы. Нужно больше практики.

eri, 1 искажение

15 минут назад. Анализируется CRC кода программы. Это новая идея. Уверен, что её можно усовершенствовать.


Ответы (9 шт):

Автор решения: CrazyElf

Есть одна мысль для одного изменения сделать набор функций с запасом и применить голосование. Если у нас три сумматора и возможна только одна ошибка, то простое большинство голосов за какой-то из вариантов позволит нам исправить ошибку.

from collections import Counter

def sum1(a, b):
    return a - b

def sum2(a, b):
    return a + b

def sum3(a, b):
    return a + b

def summator(a, b):
    return next(iter(next(iter(Counter(map(lambda x: x(a, b), [sum1, sum2, sum3])).most_common()))))
        
a, b = map(int, input().split())
print(summator(a, b))

Одна из функций испорчена (любая), но результат при этом получается корректный. Дальше нужно думать, сколько нужно копий функций, чтобы можно было правильно проголосовать ими, если ошибок станет больше.

А так то вообще можно перейти на функциональный стиль и просто избавиться от знаков арифметики. Но, думаю, вопрос был не в этом. )

a, b = map(int, input().split())
print(int.__add__(a, b))
→ Ссылка
Автор решения: Serge3leo

Вариант, на предмет одиночного искажения программы:

import statistics

def sum_one(a, b, err=0):
    return a + b if err != 1 else a - b

def sum_two(a, b, err=0):
    return a + b if err != 2 else a - b

def sum_three(a, b, err=0):
    return a + b if err != 3 else a - b

def robust_summator(abfile_one, abfile_two, abfile_three, err=0):
    return statistics.median((sum_one(*abfile_one(), err), 
                              sum_two(*abfile_two(), err), 
                              sum_three(*abfile_three(), err)))

# Вариант использования с имитацией одиночных ошибок
for a in range(-10, 11):
    for b in range(-10, 11):
        abfile = lambda: (a, b) # имитатор чтения из входного файла
        for err in range(1, 4):
            assert(robust_summator(abfile, abfile, abfile) ==
                   robust_summator(abfile, abfile, abfile, err))

Но, имеет ли смысл рассматривать одиночное искажение программы в отрыве от одиночного искажения исполняющей системы (чтения файла, int.__add__(self, other), statistics.median() и т.п.)?

P.S. Насчёт k искажений, думаю, здесь применимы оценки для блочных кодов исправляющих или корректирующих ошибки - 2k + 1 и 2k.

→ Ссылка
Автор решения: dofi4ka

Верхняя планка

Нам нужно соблюсти три условия чтобы программа выполнилась и дала неправильный ответ:

  1. принять что-то в stdin
  2. вывести любое целое число в stdout
  3. закончить исполнение без ошибок

Кратчайший код, подходящий под эти три условия:

input();exit(print(0))

Этот код можно вполне вставить в начало файла за 23 искажения. Выходит, если k >= 23, то создать устойчивый Складыватель невозможно.

k < 23

Простейшая защита

Давайте подумаем как защитить код хотя бы от одного искажения

Очевидно что для неправильного ответа достаточно лишь вставить куда-нибудь унарный минус. И пока у нас есть голые print мы вообще никак не защитимся от унарных операторов. Как вариант, вместо голого print можно использовать exec, а код Складывателя хранить в строчке, которую будем проверять чек-суммой перед запуском:

from hashlib import sha256

code = b"print(sum(map(int,input().split())))"

if sha256(code).hexdigest() == "f5fd7c191c13e9865867511719e2164c82a8bc132f67696449be642372c48996":
    exec(code)

На exec никак не повлияешь унарным оператором, а изменение переменной code сразу же повлияет на её чек-сумму!

Выходит что-то вроде двух-факторной аутентификации, нужно одновременно и изменить строчку с кодом и сломать проверку на чек-сумму. Но как вы уже поняли это всего 2 искажения:

from hashlib import sha256

code = b"print(-sum(map(int,input().split())))"
               +

if sha256(code).hexdigest() != "f5fd7c191c13e9865867511719e2164c82a8bc132f67696449be642372c48996":
                            r
    exec(code)

Получить устойчивый Складыватель можно!

Другой подход к защите и нижняя планка

Строчку с кодом можно закодировать, например в base85, и тогда просто добавить унарный оператор не выйдет

from base64 import b85encode
b85encode(b"print(sum(map(int,input().split())))")
>>> b'aB^vGbSQImZ76MFa42bRbS!CZaCLMjDK2wxY-w~TDJdx_'

Теперь просто декодируем и запускаем

from base64 import b85decode

exec(b85decode(b'aB^vGbSQImZ76MFa42bRbS!CZaCLMjDK2wxY-w~TDJdx_'))

Чек-сумма защищает всего от 1 искажения так что смысла от неё здесь особо нет

Попытаемся сломать и получим аж 17 искажений!

from base64 import b85decode

input();print(0)#exec(b85decode(b'aB^vGbSQImZ76MFa42bRbS!CZaCLMjDK2wxY-w~TDJdx_'))
+++++++++++++++++

k >= 16

Слабое место программы сейчас - это количество строчек, а именно всего одна. Из-за этого можно выйти без ошибок используя не exit, а обычной комментарий. Решение же безумно простое, растянем литерал строки на несколько строк:

from base64 import b85decode

exec(b85decode(b'aB^vGbS'
               b'QImZ76M'
               b'Fa42bRb'
               b'S!CZaCL'
               b'MjDK2wx'
               b'Y-w~TDJ'
               b'dx_'))

Теперь нужно комментировать каждую строчку и предыдущий подход потребует целых 23 искажения

просто закомментировать первую и последнюю строчку с литералом base85 не выйдет: мы получим IndentationError

k >= 22

Итог

Нижняя и верхняя планка вместе дают нам ограничение k = [22; 23), то есть мы получили 22-устойчивый Складыватель! Большую устойчивость получить невозможно так как любой Python-код можно обезвредить за 23 искажения

UPD

ниже в коментариях успели пару раз сломать мой 22-устойчивый Складыватель. Я действительно немного переоценил base85 и недооценил вашу фантазию. Что ж, вот вам моя новая версия ;)

from base64 import b85decode
exit(
    exec(
        b85decode(
            b85decode(
                b85decode(
                    b85decode(
                        b85decode(
                            b85decode(
                                b"QA18cOk-6=c5z4~Uv*+(LU>GMT1IG4J6CUfQz1(-YISO4R5MX1N-!ooD_Uh`c~VhlJzi}{WG!i8S3q=NOIIs!drML^KUjH9QZO@iSw=!oXHH&aRykKremh@cR%ka(QY3LpUR7XyS0hSwC;"
                            )
                        )
                    )
                )
            )
        )
    )
)
0/0
0/0
0/0

Жду пока кто-нибудь сломает его менее чем за 23 искажения)

UPD2

Ждать пришлось не долго :O

Но я только сейчас догадался что функцию exit() можно уместить прямо в закодированной строке. Даже не представляю что вы теперь придумаете

from base64 import b85decode
exec(
    b85decode(
        b85decode(
            b85decode(
                b85decode(
                    b85decode(
                        b85decode(
                            b'Q8;s0OIK@HPg+)Sd^BN8QB@&PPiSCFD^D#dKPp%|SV3k(USeQoR!l#AIAURFV|`M3P(W5zKu<F(VRv+WLs}tpKY3R>d?+wiOFbk}N;E++OjT4-elS!vAzC|5OLS&zPe@-deMw?GD@IyHdnPMZN=svGS3XC3Zc$k>Ei^|)WMu'
                        )
                    )
                )
            )
        )
    )
)
0/0
0/0
0/0

UPD3

Моя идея с шестью b85decode и тремя 0/0 показала себя очень хорошо, однако теперь всё ещё есть слабое место - исходная строчка.

Прежде чем показать новое решение давайте посмотрим как вообще можно сломать то что уже имеется:

  • Заменить одну из используемых функций b"...",{'int':len} в exec (12 искажений)
  • Срезать конец строки и добавить в исходник единичку b"..."[:-2]+b'+1))' (13 искажений)
  • Никто не указал, но всё так же можно вставить унарный минус: b"...".replace(b'(s',b'(-s') (22 искажения)

Третий раз вставлять свою лесенку с b85decode я не буду, кому надо: замените старый литерал на новый:

b'Q8;r{Qc77PWqVD0Z%8I#Bw2MzV_$wYKTUFdH8@&iSXLxZLvl7NPi$33Ax=z1c1cWCZDe^`V^SqPR%=RNdrf^pLS{{JN-0WBKudRaOnpQ<KUhjZD?LbIeMnJHEk<-HPCh6}LSk5OEq7y5PFibZc4<^$T469UW>7?BLM>HbbZ2{ES8-}~Q+aqnH)47wJStX6cq30kQ6_gpSw>-gHbHlKb|_RecYQ`sCSZANOLbykFiuH%C|^=GC0So(Za!soO;IFxR8?R&A$v?wD}G8+WLi#IVM8!0PE22GG)`J$QhjhnY->YJL3THAb7OKiCTLG?Awg74Pg7B5LQpbvIZ8K3Eq+yVP)T7;U?hD#M`l_)V_HQ?PbgzGa4L3XPJ3}oPd;IKXJJ}4NHb$sD_22NeQI`YP(@O0BTyz_SA0W!SABa*cXKFxSZXyuWlm33Ykp8AF<NO^W<XAHN;X1CUrAacH*RH7Q%YJ;Luf%!PD*V<LRn{1O+ZstC~HwdS660zPc3+TUR85&D^*7-DMKSzYa@LtM<{+oI8bD0Q&dcIKsRqwcu{L}MrbQmc2;aRBQi`@X?$i<VKFcyP-9~wK2B~`Bv?lzJ#=AFD^O8*SVuxJNl`^-H9$%-aV2d~LTpV(QX?=wX-!RDVJJr=LSk}bHDFLaN_=!}N>L+Cc~Me0c6@hAenV?JV{uGpDo;aqI95}1c2zxBKuL94OMGTWcSb&6Loh;8aaep&C@nWpQer79Qeu30e0NVfIc-`-ZYXA0Q7dRhIAbt+aYI5^Uw2<laU*jnVqtMORZ%urP;EtRes_3SD?VvEM`ukmPf=NHXdz8Ja3d*KY;|Z!SVd}AMoe!cHg-#MVlg>TWM6tRWp8|NOiVLXFePJpX>xK+Ktp~lQXwdNdt+2bNL5N9L`h3bb3;{cS}J!#c~K@lD_BZ-c5Eb3L~~ChSUi4ADp+oEBu7wbd?j93X;CUQLVHa>Vn|{kS!qLlBsFYPG<9HXNMk>AMpjElPBmmeHF!W{RaR3lR(x7mWlA(bLv>0pZh3iFJ$X|+RZwL&I8!liay>{UVk<>NKx$VzRbYK;O;=8SZBbM(epg3UZ*e0qVl!%0LQQU7G(}}FL`zRlDMC;'

А остальным, я думаю, куда интереснее будет посмотреть что именно там закодировано:

from hashlib import sha256
assert int("123456") == 123456
assert sum([1, 2, 3, 4, 5, 6]) == 21
assert sha256(b"print((((sum(map(int,input().split()))))))").hexdigest() == "78085bffda94487c3ca59f8da61cf5636fb990d3b79f07034d0d1b0ae598ed8f"
print((((sum(map(int,input().split()))))))
exit()

Первые два assert проверяют функции int и sum, которые раньше можно было просто заменить на len и min или max соответственно

Третий assert - защита от .replace(). Работает он очень просто: если вы придумаете как заменить несколько символов в предпоследней строке чтобы программа перестала работать, то в любом случае вы заденете и этот assert с чек-суммой

exit() теперь находится на отдельной строке, а внутри print есть несколько лишних пар скобочек. Скобочки эти разумеется не лишние: теперь если вы захотите срезать последнюю часть кода и вставить что-то своё в print, то вместо [:-2]+b'+1))' теперь придётся дописать [:-11]+b'+1))));exit()', а это уже ровно 23 искажения

Жду с нетерпеньем результатов вашей фантазии по уничтожению моего решения :)

UPD4

Мой подход к защите от срезов выше оказался неправильным. Скобочки можно спокойно обрезать с обеих сторон, чего я, оказывается, не учёл.

Теперь обрезав конец, вам придётся обратно добавлять кучу скобочек, а обрезав начало вы столкнётесь с миллионом NameError

Так же здесь добавлены дополнительные тесты

e, p, s, m, i, inp = exit, print, sum, map, int, input
assert i("123") == 123
assert i("321") == 321
assert s([1, 2, 3, 0, 0, 0]) == 6
assert s([3, 4, 5, 0, 0, 0]) == 12
assert s(m(i, "123456")) == 21
assert sha256(b"(e(p(s(m(i,inp().split())").hexdigest() == "d61f448980322eb85402b196cb190fd9c8b061bc28d944fb1e8704f5062b5623"
((((((((((((((((((((e(p(s(m(i,inp().split()))))))))))))))))))))))))

Закодированная 6 раз b85 строка:

b'Q8;r~Yf5-FL^V!vbvSEOGk!}?RZ&uRc2XpFD11mlPBckKC|^laR5UFmV@F75BVkc1X+2O&c29R<Qg<tSJY-QRd3j-XK{qW<YEULsNjM}hDP(RVZA(xjXfSa_X=impPds@kUq&q?RBTQ^b#!=HDNJNvT1F~$Qc@*+JAO?~Q8;a4eo|K>M=E}GWKcVCa5+<VeKku^MPXGWSvygDVpvK(RXkR6eJdkQN@Qm;P-c8=Yf?6RZ&^cASxr7-b3Hj`P-!qZcSK-ES$RWDb$cOUH6eCHRxo{TNmDaUJ90sHD^oC0Bw}NBL{M!jBUof#EoxC^b2cSWV@7>`Lw!s~BuQ9bby!V4BrrQrCOdXZLs2P6R7hocWPDb8K{7E-Z9Qc+PfTMnI8kCkY-me$B{@?{HE>=kNJVx|b4+g`cr#8wNj5)MPe)KiWl}{nbXP`nOm<UKJtb0NSZFv!WlCjsAy#j3RWVLpS9WAlA!2nvT0(tvepqQpCNft*NmM^eFknP|Omcd6Lr6kQJ#tTNQ)($$NMu=6RZ%%jZBSw=QYd6#cz$d~J4b9`Svf#fI9NVqJ}FIdOe=LlcQHVFS3X*KXGkkFOLsz3Vp>p8EmC1$WpQk2CR$}CGjvf#WKmF9JTiJyPdiCQPGf#$C_zzKV{1T0Y)M&VMr?aeD^xTgDLqYaQ8pu3M{PwwP-{y_eMn(4b}>&YH&$q4R#7WqVkCDla8g<&U}{TXYba4oQDk&gM=ellIZ|Uidu~!TX=^l1O>j0pPG4+JdqE*nY&&5ga79B^RbOgzQ6oM|HBnf3JaJb|a5qk3Swt&%LRcwkMqznWU^h@mYgJH9PIqr$P(prKXHr-rIb~rZVqrH?CMGC9R!uQ{Oh#y4H7!G5H!Dz4W<+RmQ+IxGYfO7fQ+Ht~M>scHMNv0MStMyxK1_08Lqkh`XE}CdQ$bosM<z5$CPykvcu7xbUPv%uBWyf<NI_6kNm@p4d1_WtBuhh0U{hmOQDb_2O-pk-L{me2S3X8dS!Go%Q6_F>OiDLtI9Nh;M|WXLBWqDIN;7_Ods8@AMpk7_G9i0NDMDX4OMYNcD?)TrB}-5!KY3A6WI|S6LU=1AWJWz}IcZjKcSTK6I6FOTPHuBMJ!5xjJW^L^c|v?-eL+}tS575sIAVBhKR#MGLNI+uD>OY|P$@ibOjk~GeQ;uUY#~EPVI)37PHK8yBV&3<X<<||NqS35cRp%PL{2<VUSwW<eNIj-J#<J;K0jwqSVuB<R77A}cPd1CGgEy}Wg}xkP(fxPWKK&-V`flfIAJ_eV?B96R5EWcQ&TfpP;pL6dU|7GGFDe6MQvhHUr;SEIcQTMM|gc%ML=>xWOHIOW@S!1LuOb<K3-5#HE%{WReMlZc2{v_K`>T)Mr<@nd?Z+RR4{aNO;RCuVpU6iabZYTJ~=p7N_;zhBT^whO>jd|O-?IDXkIlYOmuuSBUwC9N<C3%Hf?52KzB$aOEfe-XJR#EBwAB`P;5C<Q+9eaMlEY(PE1f@D{@6}OF~~+M@}VSPDgJ|Ge~v'

Жду плоды вашей фантазии :)

→ Ссылка
Автор решения: Pak Uula

Само-корректирующая программа? Даже во времена оны, когда компьютеры были скудными, и вовсю баловались с программами, которые печатают себя сами, на это забили.

Решением является доверенный Запускатель у Клиента, про который известна его целостность. Причем эта целостность обеспечивается внесистемными средствами: прошивка в Биос, проверка целостности ОЗУ (за счёт избыточных битов, радиационно-устойчивая память).

Такой Запускатель проверит целостность Складывателя при запуске и сообщит Клиенту о проблемах с кодом. Ничего лучше контрольных сумм пока не придумали.

UPD. По опыту работы с авиацией, как гражданской, так и военной.

Обновление ПО - это вот прям больное-пребольное место. В военной авиации пошли на принцип: работает, и ничего не трогай. Как на заводе залили, так и будет стоять. Любая правка ПО эквивалентна созданию новой модификации, и обновляется вся система целиком. Модели присваивается новый индекс, проводятся государственные испытания, затем борта при плановом обслуживании проходят модернизацию. Никаких "самопроверяющих" программ и обновлений кусками.

В гражданской авиации оно чуть попроще. Можно делать обновления для отдельных модулей без нового индекса и новой модификации всей линейки, но! на каждое обновление пишется просто Эверест бумаг. Описание изменений, обоснование необходимости изменений, обоснование безопасности изменений, результаты юнит тестов, результаты модульных тестов, результаты интеграционных тестов... Бинарники заливаются только с доверенных носителей с обязательным логированием "кто, что, когда" и обязательной проверкой криптографической контрольной суммы.

Вообще в системах критических по надёжности никаких искусственных интеллектов и само-исправлений ошибок. Только доказанные алгоритмы, только валидированные бинарники, только хардкор. Я с ужасом ожидаю те времена, когда конкурентная борьба вынудит системщиков ставить на борт "искусственный интеллект", который работает хрен знает как и приходит к хрен знает каким выводам.

→ Ссылка
Автор решения: eri

Можно проверить байткод программы, но её тогда нужно запускать на определенной версии python.

def main():
    a, b = map(int, input().split())
    print(a + b)

import zlib
import sys

pcrc=None

if sys.version_info[:2]==(3,10):
    pcrc = 1147345271

if sys.version_info[:2]==(3,12):
    pcrc = 2947152858

crc = zlib.crc32(main.__code__.co_code)
assert crc == pcrc, f"код поврежден: {crc} != {pcrc}"

main()

Защита байткода выглядит красиво, но сама защита не защищенна.

А вот так можно защитить и защиту тоже:

import zlib

with open(__file__,'rb') as f:
    thisfile = f.read()

crc = zlib.crc32(thisfile[:-9]).to_bytes(4,'big').hex()

if crc != thisfile[-9:-1].decode():
    raise Exception(f"код поврежден: {crc}")

else:
    del thisfile
    a, b = map(int, input().split())
    print(a + b)

0xa9fd28f7

Тут есть что оптимизировать, но сам

→ Ссылка
Автор решения: needKVAS

Я понимаю, что, вероятно, это не совсем искомый способ, но формально...

from hashlib import sha256
import requests

r = requests.get('https://raw.githubusercontent.com/im-shung/Problem-Solving/1d1565cd9d13997afd6fa5099210a2e695053442/BOJ/%EA%B8%B0%ED%83%80/1000_A%2BB.py')
exec(r.content if sha256(r.content).hexdigest() == "cb7af1a82698865433dfe854895ff32efbb7c992b7a438703187dbdabf384182" else "")

*Я не являюсь владельцем кода на гитхаб, но предполагаю, что Программист использует «доверенное» расположение файла

По сути это просто вариация «простейшей защиты» от dofi4ka

→ Ссылка
Автор решения: Yarr

В ответ на последнюю (на текущий момент) версию UPD2 от dofi4ka - я решил пойти с другой стороны и сломать программу простым переопределением методов.

Самым коротким будет переопределение sum (название из трёх букв, всего один параметр на входе, вернуть нужно число) т.е. добавив "def sum(a):return 0" + перевод строки в самое начало скрипта. Что даёт чуть меньшую границу для минимального количества искажений в 20 символов.

Так же можно поступить и с методом map, заменив его на что-то вроде: def map(*a):return{} - в таком случае мы получаем 21 символ.

П.с. извиняюсь, что не в комментариях к ответу - карма не позволяет(((

→ Ссылка
Автор решения: Dezmonder

Изначально хотел просто проверить возможность перебора файла посимвольно на предмет "разрешённых" или "запрещённых" символов. В результате получилось нечто чуть более сложное. Ломается за две подмены (если следовать моей логике), но интересно посмотреть на одну) Концептуально, не сильно отличается от вариантов, что уже есть выше, разве что этот более "человекочитаемый"

is_ok = False
with open(__file__,'r') as f:
    for l in f.read():
        if l in {'a', 't', '4', ':', '\n', 'g', 'c', '_', 'd', 'i', '(', 'k', '=', 'n', 'e', "'", 's', '.', 'x', ')', '*', 'm', 'u', 'f', 'h', '0', 'b', 'r', '"', 'o', 'l', 'E', ' ', '1', ',', '{', '}', '\\'}: is_ok = not is_ok
        elif l not in {'p', 'w', '+', 'F'}: raise Exception
a, b = map(int, input().split())
if is_ok: print(a + b)

В целом, его можно улучшить, но свою задумку я реализовал)

→ Ссылка
Автор решения: Fox Fox

Работал в банке. Читая это всё был даже удивлён.

Первое, что сразу пришло на ум: программа не должна быть текстовым скриптом! Делайте .exe! Исправляйте .exe потом, товарищи-хакеры, если кишка не тонка! Пример дан на Питоне. Там такое исправление превратится в кошмар, потому что "библиотеки поддержки" раз в сто больше самой программы. Попробуйте порыться в такой свалке.

Второе. Создаётся собственная библиотека шифрования с открытым ключом. У нас в банке такая библиотека писалась своим отделом безопасности. Программа-дешифровщик и ключ отправляется заказчику. Он расшифровывает .exe и пробует работать. Битый файл работать не будет. Ключи периодически меняются, отправляются заказчику НЕ вместе с дешифратором или зашифрованной программой, а отдельными путями. Ничто не мешает в этом случае и продублировать целостность с помощью CRC.

→ Ссылка