Я человек далекий от программирования и люблю бить баклуши. Но случилось непредвиденное, моему сербскому другу Джонни, приехавшему в гости, понадобился QR-код. Пришлось расчехлить клавиатуру.
eventWillAppear
Ко мне приехал мой сербский приятель Никола Павлович, вакцинированный2 Пфайзером. Здесь его всюду просили показать QR-код или гуляй мимо. Ситуация идиотская. Человек привит Пфайзером, а его никуда не пускают. Я был в бешенстве.
-Джонни, сгенерю-ка я тебе QR-код, - подумал я и сгенерил.
Как я узнал, как действует QR-код
Чтобы понять сценарий верификации кода, пришлось отправиться в местную Плазу и предъявить охраннику свой QR-сертификат (я вакцинирован Спутником V). Вот что я обнаружил
охранник включил телефон
охранник включил камеру на телефоне
навел камеру на мой QR-код
нажал на всплывающий поп-ап
Проходи!
На экране телефона охранника высветилась информация обо мне в стыдливо прикрытом виде
Я понял! В QR-коде просто зашифрован линк на сайт Госуслуг. В чьей-то голове сформулировалось ТЗ о разработке кода для моего серба. Голова оказалась моя. Кроме того, заметьте, информация на экране никак не может быть персональной. Аббревиатура ФИО и 3 последние цифры паспорта никак не идентифицируют человека, по таким данным даже пол гражданина (гражданки?) не определить.
Техническое задание
Надо сделать сайт-пародию (пару строки на PHP) который отображает информацию из get-request типа http://govuslugi.her/vam_qr.php?string=BBB_15031964_22345
Надо сделать iOS приложение (пару дюжин строк на swift), которое будет генерить QR-код и показывать его на экране iPhone.
Серверная часть она же Web
Вообще, публикация планировалась как учебник по SwiftUI+MVVM+Reactive, но для общего понимания задачи приведу прототип серверной части.
Я сделал веб-страничку на PHP, она будет отображать входящую информацию, записанную в параметре name нашего url-запроса. Типа www.fig.vam/vsem.php?name=jonny_depp
Я извиняюсь, последний раз я кодировал на пе-хе-пе в 2005 году, поэтому прошу прощения за олд-стайл. Слава Джобсу, в PHP не надо проверять строку на её существование и на наличие в ней символов в нужных местах, код никогда не ломается и это прекрасно. Ой, тухлые помидоры!
if (isset($_GET['name'])) $name=($_GET['name']); else $name="Xerr";
list($fio, $birth, $pass) = explode("_", $name);
$user = m_substr($fio, 0, 1, 'UTF-8')."***** ".m_substr($fio, 1, 1, 'UTF-8')."***** ".mb_substr($fio, 2, 1, 'UTF-8')."********";
$passport = "Паспорт: ".substr($pass, 0, 2)."*****".substr($pass, 2, 3);
$birthday = "Дата рождения: ".$birth;
Как видите, мы получили 3 нужные переменные, которые будем отображать на веб-страничке. П-последний раз я кодировал на Х-Т-М-Л в 1997 году, поэтому извините за олд-олд-стайл из прошлого тысячелетия.
echo '
<div class="top"><strong>
<span style="font-size:140%;color:#FF0000">QR</span>
<span style="font-size:140%;color:#6666CC">УСЛУГИ</span></strong>
<span style="color:#ffffff">BASHNI.ORG</span>
<span style="font-size:140%;color:#000000">RUS</span></div>
<div class="relative">СЕРТИФИКАТ О ГЕНЕРАЦИИ<br>QR-КОДА-19
<div class="absolute">Действителен</div>
<br><p>№ QR9290499831</p></div><div class="info"><br>'.$user.'</div>
<div class="info">'.$port.'</div>
<div class="info">Дата рождения: '.$birth.'</div>
<div class="close">Закрыть</div>';
Стили я опустил, их можно посмотреть на гитхабе. Они тупые и загадочные, как <див>ы из древних сказок Востока.
SwiftUI+MVVM
Переходим к приложению под iOS. Прости, google. Выбираем паттерн проектирования MVVM. Разумеется, для нашей маленькой задачи он избыточен, но потренироваться всегда полезно. Вдруг интервью на сеньора?? Я готов, если что, звоните от $4000. Телефон зашит в статье.
Модель содержит простейшую структуру из строки, которую надо где-то превратить в код-картинку и логическую переменную, говорящую что превращение совершилось/не совершилось.
struct UserModel {
var user: User
mutating func updateUser(name:String, birth:String, pass:String) {
let d = "_"
let temp = name+d+birth+d+pass
self.user = User(isValid: true, string: temp )
}
mutating func cleanUser() {
self.user = User(isValid: false, string: "")
}
struct User {
var isValid:Bool
var string:String
func genUrl()->String {
return urlPath+self.string
}
}
}
Полный текст файла ModelView.swift можно посмотреть на гитхабе. Вторая часть нашего паттерна - файл ContentView.swift. Он рисует в одном окне InputView ввод строки и кнопки, а во втором окне QRView изображение QR-кода. Этот SwiftUI стиль и он очень информативен - не нужны XIB-ы, верстка идет прямо в коде в джава-стиле.
import SwiftUI
struct ContentView: View {
@ObservedObject var model: ModelView
var body: some View {
if model.isValid() {
QRView(model: model)
} else {
InputView(model: model)
}
}
}
struct InputView: View {
var model: ModelView
@State private var name: String = ""
@State private var birth: String = ""
@State private var passport: String = ""
var body: some View {
VStack {
Spacer()
TextField("3 буквы ФИО", text: $name)
TextField("ДР 15.03.1964", text: $birth)
TextField("2+3 цифры паспорта", text: $passport)
Spacer()
HStack {
Button("Сделай QR", action: {
self.model.tapQRButton(name, birth, passport)
})
Spacer()
Button("Очистить", action: {
name=""
birth = ""
passport = ""
self.model.tapClean()
})
}.padding()
Spacer()
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
struct QRView: View {
var model: ModelView
@State private var isSharePresented: Bool = false
var body: some View {
VStack {
if let image = model.generateQRCode() {
Text("QR код").bold()
Text("успешно сформирован")
Spacer()
Image(uiImage: image).resizable()
.frame(width: 200, height: 200)
} else {
Text("QR код").bold().foregroundColor(.red)
Text("не сформирован").foregroundColor(.red)
Spacer()
Image(systemName: "qrcode").resizable()
.frame(width: 75, height: 75)
}
Spacer()
Button("Назад", action: { self.model.tapClean()
} ).padding()
}.padding()
Spacer()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(model: ModelView())
}
}
И, наконец, последний акт Мерлезонского балета - это файл ModelView.swift, который одним глазом глядит на ContentView и получает от него сообщения о нажатии на кнопку, а другим глазом он глядит на UserModel, куда он складывает полученную от ContentView информацию и забирает строку, которую превращает в QR-картинку и возвращает в ContentView.
//
// ModelView.swift
//
import UIKit
class ModelView: ObservableObject {
@Published private var model:UserModel = UserModel()
func isValid()->Bool {
return model.user.isValid
}
func tapQRButton(_ name:String,_ birth:String,_ pass:String) {
model.updateUser(name: name, birth: birth, pass: pass)
}
func tapClean() {
model.cleanUser()
}
func generateQRCode() -> UIImage? {
var uiImage: UIImage?
// let string = model.user.genUrl()
let string = "https://github.com/PapaBubaDiop/qrushka"
if let data = string.data(using: String.Encoding.utf8) {
if let filter = CIFilter(name: "CIQRCodeGenerator",
parameters: ["inputMessage": data,
"inputCorrectionLevel": "L"]) {
if let outputImage = filter.outputImage,
let cgImage = CIContext().createCGImage(outputImage,
from: outputImage.extent) {
let size = CGSize(width: outputImage.extent.width * 5.0,
height: outputImage.extent.height * 5.0)
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
context.interpolationQuality = .none
context.draw(cgImage,
in: CGRect(origin: .zero,
size: size))
uiImage = UIGraphicsGetImageFromCurrentImageContext()
}
UIGraphicsEndImageContext()
}
}
}
return uiImage
}
}
ModelView объявлена внутри ContentView реактивной
@ObservedObject var model: ModelView
и это значит, что графические окна перерисовываются каждый раз когда меняется содержимое нашей модели. Еще раз сценарий нашего приложения
вводим текст и нажимаем кнопку в ContentView
ModelView получает этот текст и обновляет UserModel и значит меняется сам
UserModel формирует из текста url виде строки
раз UserModel изменился, то и ModelView изменился, значит ContentView перерисовывается в соотвествии с новыми данными, которые появились в ModelView
Ура, проект готов и компилируется. No Errors No Warnings.
Запускаем приложение в симуляторе
И получаем стартовый экран. Заполняем поля в соответсвии с документом Джонни.
Нажимаем кнопку Сделай QR и получаем QR-картинку, в которой скрыт линк на наш сайт, где уже лежит php-скрипт, описанный выше. Сформированные картинки, граждане, настоящие!
Вы можете навести свой девайс на картинку и протестировать ее. Мою протестировал тот же охранник из Плазы. Welcome to Russia, Джонни!
Внимание! Никогда так не делайте! Не используйте прототип в незаконных целях! Потому что скоро это будет весить от 3-х до 5-ти.
Заключение
Вся эта история, разумеется, вымышлена от начала и до конца, но код, товарищи, настоящий! Ссылка на гитхаб прилагается.
Задачка
QR-код формируется разной зернистости в зависимости от длины строки. Мое приложение зашифровало в коды число 0 и число 1. Угадайте, где какой QR-код для 0 и 1?
Комментарии (41)
khis
25.11.2021 13:21+4А зачем так всё усложнять, если можно просто qr-код на телефон сохранить картинкой? В смысле, не вижу никаких плюшек от мобильного приложения. Добавлять людей? Так через админку на сайте.
П.С. Я против подобных экспериментов в принципе.
PapaBubaDiop Автор
25.11.2021 13:32-1Цель статьи - учебник по SwiftUI+MVVM. Не надо никаких админок и людей добавлять.
fougasse
25.11.2021 13:37+4Очевидно, что учебник можно было проиллюстрировать без нарушения закона и общей адекватности.
Также очевидно, что без хайпа на теме «сертификатов» (довольно средненькая, кстати) статейка вызовет меньший интерес.
Личное мнение
Фу быть таким, хайповать на пандемии.
PapaBubaDiop Автор
25.11.2021 13:44+1Все равно все умрем. Но некоторые перед этим поживут.
fougasse
25.11.2021 14:01-1Жить можно и без целенаправленного нарушения законов, по крайней мере, стараться.
Про рейтинги
И, да, карму минусить попрошу аргументированно
PapaBubaDiop Автор
25.11.2021 14:06+1Да нигде и ничего я не нарушил. Милая шутка, не более.
А что по теме swiftUI - почему статья слабая? Даже junior -у полезна, на мой взгляд, это не массив отсортировать и прочий мусорный контент.
frrrost
25.11.2021 14:11+7Нормальная статья. В следующий раз предлагаю рассказать, как рисовать рубли. Можно по тому же алгоритму: изучить в Плазе, как продавцы принимают за товар какие-то бумажки, а потом придумать приложение-генератор с правильными номерами серий. А то ж в Сербии рубли не выдают, надо как-то выкручиваться!
PapaBubaDiop Автор
25.11.2021 14:20Напишите, но думаю, не потянете. Имам динар овде. А в менячнице выдают рубли.
fougasse
25.11.2021 20:37Рубли неинтересно, да и могут «принять» государственные службы.
Можно «рисовать» подарочные сертификаты или купоны на скидки в супермаркете — там не так сложно, да и, вообще, делиться надо же, если экстраполировать логику «я ничего не нарушаю, ведь код смысла не имеет».
etil
25.11.2021 14:36-1Кто мешает подредактировать картинку в Paint'е и подставить туда свои данные. Разместить в облаке, расшарить и сгенерить Qr-код для нее. И не надо городить весь этот back. Только вот у проверяющих программы смотрят, и если ответ не с госуслуг, то выдает ошибку - типа "страница отсутствует"
MedicusAmicus
25.11.2021 14:44+1Даже в моих далеких гребенях на входе в ТЦ стоит охранник, вооруженный сканером, а не телефоном. И данные ищет не где предложил код, а только на ГУ.
Так что, заяц несудьбы - играться в самостоятельную генерацию документов.
Тем более, что вроде обещали давать qr-ы за титр антител тоже.PapaBubaDiop Автор
25.11.2021 15:02+5В нашей деревне нет сканеров. Только андроиды. Но их никто не расчехляет - есть QR - проходи. Половина бабушек показывает QR -код от павловской курочки.
ildarz
25.11.2021 15:06+2Кроме того, заметьте, информация на экране никак не может быть персональной. Аббревиатура ФИО и 3 последние цифры паспорта никак не идентифицируют человека, по таким данным даже пол гражданина (гражданки?) не определить.
В отдельности - нет. Но в совокупности цифры паспорта + аббревиатура + дата рождения - почти уверен, что да.
PapaBubaDiop Автор
25.11.2021 15:13+1Интересная задача. Прикинул ответ. Теоретически с точностью до 90% набор будет уникальный для 140 000 000 особей. С учетом лингвистики (русские имена) и административного деления (первые цифры паспорта)
Но, к сожалению, по ним человека не определить и кредит на него не взять)))
Gutt
25.11.2021 16:41+1Эээ... "В QR-коде закодирована ссылка на сайт, давайте сделаем поддельный сайт". Далее следуют сложные телодвижения для того, что делается с помощью любого статического хостинга и одной HTML'ки. И как человек, год назад это уже изучивший (по слегка другому поводу), я бы не стал публиковать результаты изысканий в таком виде и с таким посылом.
PapaBubaDiop Автор
25.11.2021 17:00+1Рекомендую почитать статью - она про разработку под iOS, SwiftUI и MVVM.
Пример из жизни взят лишь для мотивации разработчика.
А где все нормальные пацаны на Хабре? Куда ушли? Или все умерли?
ildarz
25.11.2021 17:04+1А где все нормальные пацаны на Хабре? Куда ушли? Или все умерли?
А тут как раз темка на эту тему недавно была - Пока, Хабр / Хабр (habr.com) :D
PapaBubaDiop Автор
25.11.2021 19:50+1Ах ты! Мир меняется. И не всегда в лучшую сторону. С другой стороны и злобные вирусы эволюционируют в ноль. Иначе мы бы вымерли...
fougasse
25.11.2021 19:56Ушли либо посмели поспорить, слили карму и теперь молчат т.к. у последователей и поддерживающих подобную «развлекуху» припекает и начинаются минуса в карму.
И тема в подтверждение.
LoadRunner
26.11.2021 11:16+1Я один заметил, что это перевод, где ссылка на оригинал ведёт на эту же самую статью?
PapaBubaDiop Автор
26.11.2021 11:52Я порезвился))
Кстати, пока готовил публикацию, редактор 8 раз зависал в САФАРИ.
Автосохранение спасало, но понервничал изрядно.
Akuma
27.11.2021 14:02+1Откуда столько хейта к автору?
Ну поигрался немного. Будет статья - без хабра разберутся.
Я вот узнал, что Свифт позволяет писать интерфейс просто в коде и выглядит это довольно просто. А последний раз я делал такое на Андройде 5 и там это жесть.
mnvcomp
28.11.2021 13:05Ну можно было просто сгенерить qr в онлайн сервисе, и скачать его в галерею на телефоне, а не писать айос приложение на свифте. А вообще, такие коды давно уже предлагают за пару тыщ рублей на всяких форумах, которые заблокированы РКН. И уже даже правительство выступило с инициативой, чтобы все коды считывали только приложением Госуслуги.Стопкоронавирус. Поэтому они в курсе про кучу поддельных сайтов. Только они не учли, что их приложение часто висит из-за больших нагрузок. Но думаю, они решат этот вопрос. Однако, не думаю, что это заставит охранников в ТЦ перейти с обычной камеры на это супер.приложение, потому что никому этот гемор с qr кодами не нужен, и совсем не спасает от коронавируса, и всем только мешает.
BaDP1nG
29.11.2021 20:45Дело не только в том, что у нас в РФ за такой "подлог" есть статья Ук, но и в том, что таким методом пользовались при предыдущей попытке ввода qr-кодов. Теперь же, например, у нас в области, субъектам МСП, оказывающим услуги населению, необходимо в заведениях использовать приложение для проверки, которое, в свою очередь, может проверить подсовываемую ей ссылку.
З.Ы. Правда, у некоторых владельцев бизнеса, для которых введено требование о проверке бизнеса, есть проблема с получением доступа в это приложение, как и с его работоспособностью, но если это гос. заказ, то чему удивляться? :-)
The_Kf
07.12.2021 23:02Такая уязвимость, с возможностью подсовывания левого сайта, в некоторых приложения проверки российких QR-кодов была. Но её с тех пор уже исправили наверняка (на проверку домена точную).
alkoro
Не знаю как в Сербии, но РФ за вот это вот всё вполне себе статья. Пишу в удалённом посте.
PapaBubaDiop Автор
Так у вас в РФ за то что без маски в тюрьму сажают. Правда, не всех...
Тут еще QR-безумие.
ПС Вообще история вымышленная, просто было интересно часок покодить.
fougasse
Вы в курсе, что в ЕС тоже за такие фокусы уголовка?
PapaBubaDiop Автор
Хабр изменился. Сербия не в ЕС. Ну и дальше по списку.
fougasse
Я в курсе, что Сербия не в ЕС, но стремится очень активно и давно.
В чём изменился Хабр? В нелюбви к хайпу?
PapaBubaDiop Автор
В неточной информации. Мне по-барабану ваш московский хайп. Я чисто по фану, извините, если это кого-то обидело.
pin2t
А какая конкретно статья, подскажете?
QR-код это не официальный документ. Нельзя подделать то что не является документом.
makapohmgn
Документом является сертификат о вакцинации, а qr это его часть.
pin2t
Так собственно с сертификатом-то автор ничего и не делает. Сертификата просто не существует.
fougasse
Я расскажу своё понимание про ЕС.
В ЕС код не содержит ссылок на портал/медицинских данных и т.п., а содержит информацию для контроля(копия того, что в бумажном сертификате) + подпись. Для его проверки интернет не нужен.
Поэтому в реалиях чуть шире, чем в РФ(а QR-код оттуда не входит в систему совместимых с EU) — QR-код является самодостаточной сущностью и представляет сертификат в другой форме, подделка которой наказуема в соответсвии с законом и попытки пользования туториалом могут закончится плачевно при первом же реальном контроле, а не просмотре кассиром в Бургер Кинге.
pin2t
Моё понимание про ЕС. В ЕС QR-коды также как в России не являются каким-то официальным документом. Никакое государство не может так быстро подтянуть законодательную базу под это.
И точно также где-то хранится ключ которым подписывают этот QR-код. И кто-то имеет к нему доступ. А значит это просто вопрос времени когда этому кому-то дадут достаточно денег, чтобы он скопировал ключ и стал продавать QR-коды на черном рынке. И это даже будут не какие-то "поддельные" QR-коды, это будут самые настоящие валидные QR-коды. Просто они не будут соответствовать факту укола прививкой.
fougasse
Они являются повторением информации с сертификата в машиночитаемом виде, а сертификат является документом.
Точно так же в коде номер сертификата и остальное. С подписью.И законодательство уже подтянуто, полиция проверяет и, как минимум, штрафует за нарушения/подделки по таким же правилам, как и с подделкой других документов по типу дипломов. До 3х лет могут дать.
Естественно, против «дать денег» ничего не сделать, но пока что такое не случилось.
Зарекался же спорить с фанатиками, слили мгновенно.