Я человек далекий от программирования и люблю бить баклуши. Но случилось непредвиденное, моему сербскому другу Джонни, приехавшему в гости, понадобился 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)


  1. alkoro
    25.11.2021 13:21
    +14

    Не знаю как в Сербии, но РФ за вот это вот всё вполне себе статья. Пишу в удалённом посте.


    1. PapaBubaDiop Автор
      25.11.2021 13:26

      Так у вас в РФ за то что без маски в тюрьму сажают. Правда, не всех...

      Тут еще QR-безумие.

      ПС Вообще история вымышленная, просто было интересно часок покодить.


      1. fougasse
        25.11.2021 13:31
        +6

        Вы в курсе, что в ЕС тоже за такие фокусы уголовка?


        1. PapaBubaDiop Автор
          25.11.2021 13:34

          Хабр изменился. Сербия не в ЕС. Ну и дальше по списку.


          1. fougasse
            25.11.2021 13:39
            +2

            Я в курсе, что Сербия не в ЕС, но стремится очень активно и давно.

            В чём изменился Хабр? В нелюбви к хайпу?


            1. PapaBubaDiop Автор
              25.11.2021 13:43

              В неточной информации. Мне по-барабану ваш московский хайп. Я чисто по фану, извините, если это кого-то обидело.


    1. pin2t
      25.11.2021 15:51
      +3

      А какая конкретно статья, подскажете?

      QR-код это не официальный документ. Нельзя подделать то что не является документом.


      1. makapohmgn
        25.11.2021 16:57

        Документом является сертификат о вакцинации, а qr это его часть.


        1. pin2t
          25.11.2021 19:33
          +2

          Так собственно с сертификатом-то автор ничего и не делает. Сертификата просто не существует.


      1. fougasse
        25.11.2021 20:33

        Я расскажу своё понимание про ЕС.

        В ЕС код не содержит ссылок на портал/медицинских данных и т.п., а содержит информацию для контроля(копия того, что в бумажном сертификате) + подпись. Для его проверки интернет не нужен.

        Поэтому в реалиях чуть шире, чем в РФ(а QR-код оттуда не входит в систему совместимых с EU) — QR-код является самодостаточной сущностью и представляет сертификат в другой форме, подделка которой наказуема в соответсвии с законом и попытки пользования туториалом могут закончится плачевно при первом же реальном контроле, а не просмотре кассиром в Бургер Кинге.


        1. pin2t
          26.11.2021 08:10
          +1

          Моё понимание про ЕС. В ЕС QR-коды также как в России не являются каким-то официальным документом. Никакое государство не может так быстро подтянуть законодательную базу под это.

          И точно также где-то хранится ключ которым подписывают этот QR-код. И кто-то имеет к нему доступ. А значит это просто вопрос времени когда этому кому-то дадут достаточно денег, чтобы он скопировал ключ и стал продавать QR-коды на черном рынке. И это даже будут не какие-то "поддельные" QR-коды, это будут самые настоящие валидные QR-коды. Просто они не будут соответствовать факту укола прививкой.


          1. fougasse
            26.11.2021 12:36

            Они являются повторением информации с сертификата в машиночитаемом виде, а сертификат является документом.

            Точно так же в коде номер сертификата и остальное. С подписью.И законодательство уже подтянуто, полиция проверяет и, как минимум, штрафует за нарушения/подделки по таким же правилам, как и с подделкой других документов по типу дипломов. До 3х лет могут дать.

            Естественно, против «дать денег» ничего не сделать, но пока что такое не случилось.

            Зарекался же спорить с фанатиками, слили мгновенно.


  1. khis
    25.11.2021 13:21
    +4

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

    П.С. Я против подобных экспериментов в принципе.


    1. PapaBubaDiop Автор
      25.11.2021 13:32
      -1

      Цель статьи - учебник по SwiftUI+MVVM. Не надо никаких админок и людей добавлять.


      1. fougasse
        25.11.2021 13:37
        +4

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

        Также очевидно, что без хайпа на теме «сертификатов» (довольно средненькая, кстати) статейка вызовет меньший интерес.

        Личное мнение

        Фу быть таким, хайповать на пандемии.


        1. PapaBubaDiop Автор
          25.11.2021 13:44
          +1

          Все равно все умрем. Но некоторые перед этим поживут.


          1. fougasse
            25.11.2021 14:01
            -1

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

            Про рейтинги

            И, да, карму минусить попрошу аргументированно


            1. PapaBubaDiop Автор
              25.11.2021 14:06
              +1

              Да нигде и ничего я не нарушил. Милая шутка, не более.

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


  1. davidovsv
    25.11.2021 13:59

    Это гениально, бро!


  1. frrrost
    25.11.2021 14:11
    +7

    Нормальная статья. В следующий раз предлагаю рассказать, как рисовать рубли. Можно по тому же алгоритму: изучить в Плазе, как продавцы принимают за товар какие-то бумажки, а потом придумать приложение-генератор с правильными номерами серий. А то ж в Сербии рубли не выдают, надо как-то выкручиваться!


    1. PapaBubaDiop Автор
      25.11.2021 14:20

      Напишите, но думаю, не потянете. Имам динар овде. А в менячнице выдают рубли.


    1. fougasse
      25.11.2021 20:37

      Рубли неинтересно, да и могут «принять» государственные службы.

      Можно «рисовать» подарочные сертификаты или купоны на скидки в супермаркете — там не так сложно, да и, вообще, делиться надо же, если экстраполировать логику «я ничего не нарушаю, ведь код смысла не имеет».


      1. frrrost
        25.11.2021 20:43

        А за фишинг государственного сайта принять не могут?


  1. etil
    25.11.2021 14:36
    -1

    Кто мешает подредактировать картинку в Paint'е и подставить туда свои данные. Разместить в облаке, расшарить и сгенерить Qr-код для нее. И не надо городить весь этот back. Только вот у проверяющих программы смотрят, и если ответ не с госуслуг, то выдает ошибку - типа "страница отсутствует"


  1. MedicusAmicus
    25.11.2021 14:44
    +1

    Даже в моих далеких гребенях на входе в ТЦ стоит охранник, вооруженный сканером, а не телефоном. И данные ищет не где предложил код, а только на ГУ.
    Так что, заяц несудьбы - играться в самостоятельную генерацию документов.
    Тем более, что вроде обещали давать qr-ы за титр антител тоже.


    1. PapaBubaDiop Автор
      25.11.2021 15:02
      +5

      В нашей деревне нет сканеров. Только андроиды. Но их никто не расчехляет - есть QR - проходи. Половина бабушек показывает QR -код от павловской курочки.


  1. ildarz
    25.11.2021 15:06
    +2

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

    В отдельности - нет. Но в совокупности цифры паспорта + аббревиатура + дата рождения - почти уверен, что да.


    1. PapaBubaDiop Автор
      25.11.2021 15:13
      +1

      Интересная задача. Прикинул ответ. Теоретически с точностью до 90% набор будет уникальный для 140 000 000 особей. С учетом лингвистики (русские имена) и административного деления (первые цифры паспорта)

      Но, к сожалению, по ним человека не определить и кредит на него не взять)))


  1. Gutt
    25.11.2021 16:41
    +1

    Эээ... "В QR-коде закодирована ссылка на сайт, давайте сделаем поддельный сайт". Далее следуют сложные телодвижения для того, что делается с помощью любого статического хостинга и одной HTML'ки. И как человек, год назад это уже изучивший (по слегка другому поводу), я бы не стал публиковать результаты изысканий в таком виде и с таким посылом.


    1. PapaBubaDiop Автор
      25.11.2021 17:00
      +1

      Рекомендую почитать статью - она про разработку под iOS, SwiftUI и MVVM.

      Пример из жизни взят лишь для мотивации разработчика.

      А где все нормальные пацаны на Хабре? Куда ушли? Или все умерли?


      1. ildarz
        25.11.2021 17:04
        +1

        А где все нормальные пацаны на Хабре? Куда ушли? Или все умерли?

        А тут как раз темка на эту тему недавно была - Пока, Хабр / Хабр (habr.com) :D


        1. PapaBubaDiop Автор
          25.11.2021 19:50
          +1

          Ах ты! Мир меняется. И не всегда в лучшую сторону. С другой стороны и злобные вирусы эволюционируют в ноль. Иначе мы бы вымерли...


      1. fougasse
        25.11.2021 19:56

        Ушли либо посмели поспорить, слили карму и теперь молчат т.к. у последователей и поддерживающих подобную «развлекуху» припекает и начинаются минуса в карму.

        И тема в подтверждение.


  1. LoadRunner
    26.11.2021 11:16
    +1

    Я один заметил, что это перевод, где ссылка на оригинал ведёт на эту же самую статью?


    1. PapaBubaDiop Автор
      26.11.2021 11:52

      Я порезвился))

      Кстати, пока готовил публикацию, редактор 8 раз зависал в САФАРИ.

      Автосохранение спасало, но понервничал изрядно.


  1. gearbox
    26.11.2021 20:38

    То самое чувство когда ты осознаешь насколько чужеродно смотрится на новом хабре статья одного из двух любимых авторов (второй - @Milfgard) и понимаешь в этот момент что хабр который я любил реально умирает.


  1. Akuma
    27.11.2021 14:02
    +1

    Откуда столько хейта к автору?

    Ну поигрался немного. Будет статья - без хабра разберутся.

    Я вот узнал, что Свифт позволяет писать интерфейс просто в коде и выглядит это довольно просто. А последний раз я делал такое на Андройде 5 и там это жесть.


    1. PapaBubaDiop Автор
      27.11.2021 23:18

      SwiftUI просто восторг.


  1. mnvcomp
    28.11.2021 13:05

    Ну можно было просто сгенерить qr в онлайн сервисе, и скачать его в галерею на телефоне, а не писать айос приложение на свифте. А вообще, такие коды давно уже предлагают за пару тыщ рублей на всяких форумах, которые заблокированы РКН. И уже даже правительство выступило с инициативой, чтобы все коды считывали только приложением Госуслуги.Стопкоронавирус. Поэтому они в курсе про кучу поддельных сайтов. Только они не учли, что их приложение часто висит из-за больших нагрузок. Но думаю, они решат этот вопрос. Однако, не думаю, что это заставит охранников в ТЦ перейти с обычной камеры на это супер.приложение, потому что никому этот гемор с qr кодами не нужен, и совсем не спасает от коронавируса, и всем только мешает.


  1. BaDP1nG
    29.11.2021 20:45

    Дело не только в том, что у нас в РФ за такой "подлог" есть статья Ук, но и в том, что таким методом пользовались при предыдущей попытке ввода qr-кодов. Теперь же, например, у нас в области, субъектам МСП, оказывающим услуги населению, необходимо в заведениях использовать приложение для проверки, которое, в свою очередь, может проверить подсовываемую ей ссылку.

    З.Ы. Правда, у некоторых владельцев бизнеса, для которых введено требование о проверке бизнеса, есть проблема с получением доступа в это приложение, как и с его работоспособностью, но если это гос. заказ, то чему удивляться? :-)


  1. The_Kf
    07.12.2021 23:02

    Такая уязвимость, с возможностью подсовывания левого сайта, в некоторых приложения проверки российких QR-кодов была. Но её с тех пор уже исправили наверняка (на проверку домена точную).