[Polish] Szyfrowanie plików za pomocą YubiKey

in #polish21 days ago

Bezpieczne przechowywanie danych wiąże się między innymi z kopią bezpieczeństwa, którą można odtworzyć w razie awarii. Być może przechowujesz swoje dane na własnym NAS, być może to jest właśnie Twoja druga kopia. Może jednak dysk na twoim komputerze roboczym/serwerze/NAS jest tą pierwszą kopią, a kopia bezpieczeństwa jest przechowywana na komputerze kogoś innego (czytaj: w chmurze). W tym wypadku, ponieważ jesteś człowiekiem rozumnym, wszystkie Twoje prywatne dane na czyimś komputerze są przez ciebie zaszyfrowane przed wrzuceniem ich tam, wiadomo.

Szyfrowanie symetryczne, a asymetryczne

W okoliczności szyfrowanej kopii zapasowej najczęstszym chyba scenariuszem jest szyfrowanie symetryczne. Używamy hasła, które pamiętamy lub mamy gdzieś zapisane. To samo hasło jest potrzebne do rozszyfrowania plików. Proste. Potencjalne problemy to jednak wyciek haseł, konieczność ich ciągłego wpisywania/wklejania, nadużywanie jednego hasła do wielu miejsc, czy też… symetria właśnie. Załóżmy bowiem, że chcesz zautomatyzować proces tworzenia kopii zapasowej na swoim serwerze/NAS. Aby serwer wrzucił pliki do chmury musi je wcześniej zaszyfrować, a aby je zaszyfrować symetrycznie musi znać hasło. Hasło jest więc gdzieś tam zapisane w potencjalnie łatwy do odzyskania (ukradzenia) sposób. Jeśli zechcemy mieć wiele różnych haseł dostajemy już zbiór haseł i generalnie zaczyna się bałagan.

W sortowaniu asymetrycznym nie mamy hasła jako-takiego, tylko dwa klucze. Jeden to klucz publiczny, a drugi prywatny. Klucz publiczny pozwala tylko zaszyfrować dane. Jeśli wycieknie, nic się nie stanie. Nie da się nim rozszyfrować danych. Nie da się z niego też uzyskać klucza prywatnego. To jest coś, co mógłbyś powierzyć swojej maszynie nie popadając w paranoję pytań w stylu „czy to hasło na pewno jest tu bezpieczne”. Klucz prywatny natomiast przyda się dopiero jak przyjdzie do odszyfrowania kopii (rzadko). Taki klucz generuje komputer i jest on bardzo trudny do odgadnięcia. Trzeba go jednak przechowywać. Właśnie – gdzie? W tym artykule przyjrzę się możliwości wykorzystania klucza sprzętowego YubiKey do stosowania kluczy prywatnych używanych do szyfrowania asymetrycznego. "Stosowania", a nie "przechowywania", bo z YubiKey klucza prywatnego nie da się wyjąć (bezpieczeństwo), można go jednak używać. Innymi słowy: jak szyfrować YubiKey-em?

Wymagania

Opisywane tu sposoby powinny zadziałać na różnych systemach operacyjnych (w szczególności pod Windows i Linux). Wymagane oprogramowanie:

  • OpenSSL (większość Linuksów już ma ten pakiet po instalacji systemu, dla innych systemów zobacz sekcję Binaries, pod Windows może to być np. wersja z instalatorem od FireDaemon – wtedy zaznacz przy instalacji, że chcesz dodania tego narzędzia do zmiennej PATH).
  • Oprogramowanie do obsługi smart cards. Może to być OpenSC Project (https://github.com/OpenSC/OpenSC/releases) w „kompletnej” instalacji (wszystkie komponenty).
  • Oprogramowanie, które ma opcje wrzucania certyfikatów na YubiKey. Może to być YubiKey PIV Manager, YubiKey Manager, od niedawna taką funkcję ma też Yubico Authenticator, acz jeszcze jej w nim nie sprawdzałem.

Konieczna będzie też umiejętność obsługi wiersza poleceń (terminal).

Idea

Przygotowanie (jednorazowo):

  • Wygenerujemy klucze na bezpiecznym, prywatnym komputerze.
  • Wgramy klucze na YubiKey.
  • Klucz publiczny oprócz tego pozostanie zwykłym plikiem, który skopiujemy wszędzie, gdzie będzie to potrzebne (np. na NAS, który będzie robił zaszyfrowane kopie swoich danych).

Aby zaszyfrować plik:

  • Wygenerujemy długie losowe hasło (dla każdego pliku inne).
  • Tym hasłem zaszyfrujemy plik z danymi (w ten sposób powstanie plik „.enc”)
  • Zaszyfrujemy to hasło kluczem publicznym i zapiszemy do pliku (w ten sposób powstanie plik „.key”).

Aby rozszyfrować plik:

  • Każemy YubiKey rozszyfrować hasło (plik „.key”) kluczem, który on przechowuje.
  • Rozszyfrowanym hasłem rozszyfrujemy plik z danymi „.enc”.

Dlaczego taka komplikacja? Miało być asymetrycznie, a jednak sam plik jest szyfrowany symetrycznym hasłem? Tak. Da się użyć klucza publicznego, aby zaszyfrować plik bezpośrednio (czyli bez pliku z hasłem, bez pliku „.key”). Gorzej z rozszyfrowaniem. Nie tylko YubiKey nie przyjmie tak długiego ciągu danych do rozszyfrowania, ale nawet gdyby to zrobił, rozszyfrowywanie byłoby bardzo powolne. Procesor w twoim komputerze jest po prostu dewastująco szybszy od procesora zawartego w YubiKey. Co więcej, to daje nam elastyczność w wyborze algorytmów i narzędzi, bo tylko jeden z kroków musi być obsługiwany przez YubiKey. YubiKey zajmie się więc tylko hasłami.

Inne hasło do każdego pliku to też dodatkowe zabezpieczenie. Atakujący nawet jeśli przechwyci wiele przykładów twoich zaszyfrowanych plików („.key” i „.enc”) wraz z rozszyfrowanymi plikami (zakładając, że nie będziesz przechowywać rozszyfrowanych haseł, bo po co), to porównanie ich w celu „wykopania” twojego klucza prywatnego będzie niesłychanie skomplikowane obliczeniowo (czytaj: niemożliwe).

Przygotowanie kluczy

Na prywatnym komputerze wejdźmy w terminalu do jakiegoś pustego katalogu roboczego (najlepiej jakiegoś, do którego inni użytkownicy systemu operacyjnego nie mają dostępu nawet do odczytu) i wykonajmy polecenie:
openssl genrsa -out key.pem 2048
To wygeneruje losowy klucz prywatny i zapisze go do pliku „key.pem”.
Potem wygenerujemy z niego klucz publiczny:
openssl rsa -in key.pem -outform PEM -pubout -out public.pem
YubiKey nie będzie chciał „łysego” klucza prywatnego, a cały certyfikat. Wygenerujmy go tak:
openssl req -new -x509 -key key.pem -out cacert.pem -days 9999
Program spyta o detale certyfikatu. Do naszego zastosowania nie mają znaczenia. Można pozatwierdzać puste ciągi enterem. Tylko jako „Common Name” podajmy coś co zidentyfikuje ten certyfikat lub jego właściciela (wiele narzędzi potrafi wyświetlać to pole pokazując, co jest wgrane na YubiKey). Parametr „days 9999” ustali, że certyfikat będzie ważny ponad 27 lat. Tyle czasu powinno wystarczyć, aby skończyć czytać ten artykuł.

Jeśli wszystko dobrze poszło powinno dać się taką komendą wygenerować plik, który będzie zawierał po sobie zarówno klucz publiczny, jak i certyfikat (to wszystko są pliki tekstowe, więc można je podglądać i porównywać):
openssl x509 -inform PEM -in cacert.pem -pubkey > public-and-cacert.pem

Teraz uruchamiamy narzędzie do wgrywania kluczy PIV. Wspominałem, że twój model YubiKey musi obsługiwać PIV?

Yubico Authenticator.png
Dostępność funkcji PIV widoczna w Yubico Authenticator

Narzędzie może spytać, czy chcesz nadać kod PIN, PUK i hasło zarządzania kluczami. Nadanie hasła zarządzania kluczami to dobra decyzja, bo jego nieznajomość uniemożliwi podmianę lub skasowanie certyfikatu. W ten sposób złośliwe oprogramowanie nie namiesza ci w YubiKey. Sam też nie namieszasz, bo widząc prośbę o to hasło uświadomisz sobie, że coś zmieniasz, a może niekoniecznie chcesz. Można też nadać kod PIN – wtedy będzie on potrzebny zawsze przy rozszyfrowaniu pliku. PUK pomaga gdy zapomnimy PIN.

Wgraj wygenerowany certyfikat („Import file”) do YubiKey swoim narzędziem z GUI. W zależności od narzędzia jakiego użyjesz odpowiednim plikiem będzie „cacert.pem” lub „public-and-cacert.pem”. Wgraj go na pozycję „Key Management”. W moim YubiKey to jest pozycja oznaczona również jako „9d”, ale w innych kluczach może być inaczej.

Jeśli dobrze poszło będzie się dało wyjąć klucz publiczny z YubiKey. Użyjmy narzędzia „pkcs11-tool.exe”. Przedstawię składnię dla Windows, bo jest bardziej skomplikowana (wymaga podania pliku „.dll”):
"C:\Program Files\OpenSC Project\OpenSC\tools\pkcs11-tool.exe" --read-object --type pubkey --id 3 -m RSA-PKCS --module "C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll" -o public.der
Jeśli nie działa, to spróbuj inne „id” niż „3”.
To da plik w formacie “.der”, który da się skonwertować do bardziej zjadliwego „.pem” w taki sposób:
openssl rsa -pubin -inform DER -in public.der -outform PEM -out public-from-yubikey.pem
Tym sposobem można też napisać skrypt, który zaszyfruje nam pliki nie posiadając pliku z kluczem publicznym. Taki skrypt może sam go wyjąc z YubiKey używając tych dwóch komend powyżej.

Key generation PL.png

Po tym wszystkim należy zastanowić się co zrobić z kluczem prywatnym (i/lub certyfikatem). Jest już wgrany do YubiKey, ale ewentualne zgubienie YubiKey uniemożliwi odzyskanie zawartości zaszyfrowanych kopii. Osobiście mam dwa identyczne klucze YubiKey więc zwyczajnie powtórzyłem procedurę dla drugiego urządzenia i skasowałem pliki z dysku („key.pem”, „cacert.pem”, „public-and-cacert.pem”). W twoim scenariuszu może odpowiedniejsze będzie zachowanie kopii gdzieś w bezpiecznym miejscu, może na jakimś zewnętrznym ukrytym nośniku. Na pewno nie wysyłaj go do chmury.

Szyfrowanie pliku

Stwórzmy teraz skrypt, który będzie szyfrował pliki. Podane komendy złożysz samodzielnie w coś, co będzie ci odpowiadać. Pominę tu same przygotowanie pliku do szyfrowania (jego wybór, być może stworzenie go, jeśli to ma być archiwum itp.).

Zacznijmy od wygenerowania losowego hasła. Popularne na Linuksie rozwiązanie, aby wygenerować 64 alfanumeryczne znaki, to:
tr -cd '[:alnum:]' < /dev/urandom | fold -w64 | head -n1
Skoro jednak i tak wymagamy do działania pakietu OpenSSL napiszmy coś, co zadziała na większej liczbie platform:
openssl rand 48 | base64 --wrap=0
To wygeneruje 65 znaków ze zbioru alfanumerycznych oraz „+” i „/”. Niezaszyfrowanego hasła nie chcemy w ogóle nigdzie zapisywać na dysku (nawet na chwilę), więc w skrypcie Bash (Linux) możemy zrobić tak, aby wpakować hasło od razu do zmiennej:
PLAIN_PASS=$(openssl rand 48 | base64 --wrap=0)

Teraz aby zaszyfrować i zapisać to hasło zrobimy:

PUBLIC_KEY="/root/.ssl-keys/yubikey.pem"
echo -n "$PLAIN_PASS" | openssl pkeyutl -encrypt -inkey "$PUBLIC_KEY" -pubin > output.key

W pierwszej linii zdefiniowaliśmy miejsce, gdzie leży plik z kluczem publicznym. W drugiej wyjmujemy, co zostało wpisane wcześniej do zmiennej „PLAIN_PASS”, szyfrujemy to kluczem publicznym i zapisujemy rezultat do pliku „output.key”. To będzie pierwszy z naszych plików wynikowych. Teraz pora zaszyfrować plik właściwy.
echo -n "$PLAIN_PASS" | openssl enc -aes-256-cbc -md sha256 -iter 100000 -salt -in "$1" -pass stdin > output.enc
W miejsce „$1” należy wstawić plik wejściowy do zaszyfrowania. Jeśli zostawimy tu „$1”, to użyty zostanie argument, jaki podano odpalając skrypt.

W rezultacie po tym wszystkim otrzymamy dwa pliki wyjściowe: „output.key” i „output.enc”. Możemy oba bezpiecznie umieścić w chmurze. Powinny zawsze podróżować razem.

Encryption PL.png

Deszyfrowanie pliku

Po lekturze poprzednich etapów wiadomo już czego się tutaj spodziewać. Potrzebujemy najpierw rozszyfrować hasło, potem tym hasłem plik. Aby rozszyfrować hasło musimy poprosić o to YubiKey. Narzędzie „pksc11-tool” ma do tego komendę „decrypt”. Znów podam składnię dla Windows, ponieważ jest bardziej skomplikowana:
"C:\Program Files\OpenSC Project\OpenSC\tools\pkcs11-tool.exe" --id 3 --decrypt -m RSA-PKCS --module "C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll" --input-file input.key
Należy podać taki sam argument „id”, jaki zadziałał przy pobieraniu klucza publicznego. Jeśli wgrałeś certyfikat do YubiKey na pozycję „Key Management”, najprawdopodobniej będzie to „3”, ale może to zależeć od modelu YubiKey. Zamiast „input.key” podaj plik wejściowy z zaszyfrowanym hasłem (plik „.key”). Wynik zostanie wyświetlony na ekranie, więc w skrypcie pewnie zechcesz zapisać go do zmiennej lub skierować za pomocą „>” do pliku tymczasowego (to drugie jest mniej bezpieczne).

Znając rozszyfrowane hasło możemy rozszyfrować plik:
openssl enc -d -aes-256-cbc -md sha256 -iter 100000 -in input.enc -out output
Ta komenda weźmie zaszyfrowany plik wejściowy z danymi „input.enc”, rozszyfruje go i zapisze wynik jako plik „output”. Plik wejściowy może być dowolnej wielkości. Odpalając to w takiej postaci program spyta nas interaktywnie o podanie hasła. Jeśli chcemy przekazać rozszyfrowane przed chwilą hasło automatycznie, trzeba dodać parametr „-pass”. Aby odczytać hasło z pliku (mniej bezpieczne) trzeba dodać przedrostek „file:” (potem ścieżka do pliku):
openssl enc -d -aes-256-cbc -md sha256 -iter 100000 -in input.enc -out output -pass file:path_to_password_file
Aby odczytać hasło ze zmiennej (lepszy pomysł) trzeba dodać przedrostek „env:” (potem nazwa zmiennej):
openssl enc -d -aes-256-cbc -md sha256 -iter 100000 -in input.enc -out output -pass env:PLAIN_PASS

Integracja graficzna

Manipulacja komendami to wszystko, co potrzebne, aby napisać odpowiednie skrypty do wykonywania lub przywracania zaszyfrowanych kopii zapasowych. Czasem jednak wygodniej używać narzędzi graficznych do szyfrowania lub deszyfrowania. Wymienione powyżej komendy można zintegrować do swojego ulubionego narzędzia zarządzania plikami. Przykładowa prosta integracja z Directory Opus 12 lub nowszym wyglądać będzie jak wkleiłem na sam dół tego artykułu. Ten tekst źródłowy należy wkleić do nowego pliku (o nazwie np. „Yubikey.js”). W notatniku przed zapisem warto zmienić linię z "C:\\Windows\\Icons\\Yubikey 1.ico" na ścieżkę do jakiejś swojej domyślnej ikony operacji szyfrowania/deszyfrowania z YubiKey. Można znaleźć dowolne w Internecie, moje wyglądają jak poniżej.

YubiKey Icons.png

W Directory Opus skrypt zainstaluje się wybierając ustawienia skryptów („Zarządzanie skryptami”), tam „Instaluj” i wybierając plik ze skryptem. Skrypt pod ikonką zębaki w tym samym oknie udostępnia opcje wyboru ścieżki do OpenSSL i OpenSC oraz poprawnego numeru ID z certyfikatem w YubiKey (trzeba użyć tego samego, co używano z narzędziem „pkcs11-tool” z OpenSC). W rezultacie Directory Opus wzbogaci się o komendę YubiKeyEncrypt do szyfrowania plików z YubiKey, która z parametrem REVERSE posłuży do rozszyfrowania plików. Użyć tej komendy można w skrypcie Directory Opus albo zwyczajnie definiując nowy przycisk do funkcji szyfrowania/deszyfrowania wybranych plików (prawy klawisz na panel i „Dostosuj…”, potem znów prawym i „Nowy element” – „Nowy przycisk”).

DOpus Encrypt.png

DOpus Decrypt.png

Skrypt JS (JavaScript):

// Global scope, so object will not be created for each item
var shell = new ActiveXObject("WScript.Shell");

var OK = 1;
var SKIP = 2;
var CANCEL = 0;
var ABORT = "a";

function OnInit(initData) {
    initData.name = Str("script_name");
    initData.desc = Str("script_desc");
    initData.version = "1.0";
    initData.copyright = "Mario";
    initData.default_enable = true;
    initData.min_version = "12.27";
    
    initData.config.openssl="C:\\Program Files\\OpenSSL";
    initData.config.opensc="C:\\Program Files\\OpenSC Project\\OpenSC";
    initData.config.pksc_id=3;
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = "YubiKeyEncrypt";
    cmd.method = "OnEncrypt";
    cmd.desc = Str("cmd_desc");
    cmd.label = Str("cmd_name");
    cmd.icon = "C:\\Windows\\Icons\\Yubikey 1.ico"; // Use your own icon
    cmd.template = "REVERSE/S,HERE/S";
}

function OnEncrypt(data) {
    if (!CheckConfig()) return;
    var progress = InitProgress(data);
    
    var sslTool = GetSslTool();
    var pkscTool = GetPkscTool();
    var pkscDll = GetPkscDll();
    var pkscId = GetPkscId();
    var here = data.func.args.here; // Operate on same dir instead of destination dir
    
    var enumFiles = new Enumerator(data.func.sourcetab.selected_files);
    
    if (data.func.args.reverse) { // Decrypt
        Decrypt(data, enumFiles, progress, here, sslTool, pkscTool, pkscDll, pkscId);
    } else { // Encrypt
        var pubKey = FetchPubKeyFromYubiKey(data, sslTool, pkscTool, pkscDll, pkscId);
        if (pubKey == null) {
            Error(data, Str("error_encrypt_fail_cert_read"));
        } else {
            Encrypt(data, enumFiles, progress, here, pubKey, sslTool, pkscTool, pkscDll, pkscId);
            DeleteTempFile(pubKey);
        }
    }
    
    progress.Hide();
}

function Decrypt(data, enumFiles, progress, here, sslTool, pkscTool, pkscDll, pkscId) {
    var decrypted = DOpus.Create.StringSetI();
    
    while (!enumFiles.atEnd()) {
        var currentFile = enumFiles.item();
        if (currentFile.is_dir) {
            Error(data, Str("error_decrypt_dir"));
            data.func.command.RemoveFile(currentFile);
            return;
        }
        currentFile = currentFile.realpath;
        
        progress.SetName(currentFile);
        progress.Show();
        if (progress.GetAbortState(true, ABORT, true) == ABORT) {
            return;
        }
        
        var encFile = ToEncFile(currentFile);
        if (encFile == null) {
            Error(data, Str("error_decrypt_no_enc") + currentFile.filepart);
            data.func.command.RemoveFile(currentFile);
        } else if (decrypted.insert(encFile)) { // Don't decrypt twice
            var keyFile = ToKeyFile(currentFile);
            if (keyFile == null) {
                Error(data, Str("error_decrypt_no_key") + currentFile.filepart);
                data.func.command.RemoveFile(currentFile);
            } else {
                if (here || data.func.desttab.path == null) {
                    var outFile = DOpus.FSUtil.NewPath(currentFile.pathpart);
                } else {
                    var outFile = DOpus.FSUtil.NewPath(data.func.desttab.path);
                }
                outFile.Add(currentFile.stem);
                
                if (progress.GetAbortState(true, ABORT, true) == ABORT) {
                    return;
                }
                
                var replace = OK;
                if (DOpus.FSUtil.Exists(outFile)) {
                    replace = AskReplace(data, outFile);
                    if (replace == CANCEL) {
                        return;
                    } else if (replace == SKIP) {
                        data.func.command.RemoveFile(currentFile);
                    }
                }
                
                if (progress.GetAbortState(true, ABORT, true) == ABORT) {
                    return;
                }
                
                if (replace == OK) { // OK to replace or dest file is not there yet
                    var tempFile = GetTempFile();
                    
                    // Decrypt key
                    var result = Execute('"' + pkscTool + '" --id ' + pkscId
                        + ' --decrypt -m RSA-PKCS --module "' + pkscDll + '" --input-file "'
                        + keyFile + '" --output-file "' + tempFile + '"');
                        
                    if (!result) {
                        Error(data, Str("error_decrypt_fail_key") + currentFile.filepart);
                        data.func.command.RemoveFile(currentFile);
                    } else if (progress.GetAbortState(false, ABORT, true) != ABORT) {
                        // Decrypt data
                        ExecuteSilent(data.func.command, '"' + sslTool
                            + '" enc -d -aes-256-cbc -md sha256 -iter 100000 -in "' + encFile
                            + '" -out "' + outFile + '" -pass "file:' + tempFile + '"');
                    }
                    
                    DeleteTempFile(tempFile);
                }
            }
        }
        
        enumFiles.moveNext();
        progress.StepFiles(1);
    }
}

function Encrypt(data, enumFiles, progress, here, pubKey, sslTool, pkscTool, pkscDll, pkscId) {
    while (!enumFiles.atEnd()) {
        var currentFile = enumFiles.item();
        if (currentFile.is_dir) {
            Error(data, Str("error_encrypt_dir"));
            data.func.command.RemoveFile(currentFile);
            return;
        }
        currentFile = currentFile.realpath;
        
        progress.SetName(currentFile);
        progress.Show();
        if (progress.GetAbortState(true, ABORT, true) == ABORT) {
            return;
        }
        
        if (here || data.func.desttab.path == null) {
            var encFile = DOpus.FSUtil.NewPath(currentFile.pathpart);
            var keyFile = DOpus.FSUtil.NewPath(currentFile.pathpart);
        } else {
            var encFile = DOpus.FSUtil.NewPath(data.func.desttab.path);
            var keyFile = DOpus.FSUtil.NewPath(data.func.desttab.path);
        }
        encFile.Add(currentFile.filepart + ".enc");
        keyFile.Add(currentFile.filepart + ".key");
                                
        var replace = OK;
        if (DOpus.FSUtil.Exists(encFile)) {
            replace = AskReplace(data, encFile);
            if (replace == CANCEL) {
                return;
            } else if (replace == SKIP) {
                data.func.command.RemoveFile(currentFile);
            }
        }
        if (progress.GetAbortState(true, ABORT, true) == ABORT) {
            return;
        }
        if (replace == OK) { // OK to replace or dest ENC file is not there yet
            if (DOpus.FSUtil.Exists(keyFile)) {
                replace = AskReplace(data, keyFile);
                if (replace == CANCEL) {
                    return;
                } else if (replace == SKIP) {
                    data.func.command.RemoveFile(currentFile);
                }
            }
            if (progress.GetAbortState(true, ABORT, true) == ABORT) {
                return;
            }
            
            if (replace == OK) { // OK to replace or dest KEY file is not there yet
                var plainKeyFile = GetRandomTempFile(data);
                
                // Encrypt key
                ExecuteSilent(data.func.command, '"' + sslTool + '" pkeyutl -encrypt -inkey "'
                    + pubKey + '" -pubin -in "' + plainKeyFile + '" -out "' + keyFile + '"');
                    
                if (!DOpus.FSUtil.Exists(keyFile) || IsEmptyFile(keyFile)) {
                    Error(data, Str("error_encrypt_fail_key") + "\n" + keyFile);
                } else if (progress.GetAbortState(false, ABORT, true) != ABORT) {
                    // Encrypt data
                    ExecuteSilent(data.func.command, '"' + sslTool + '" enc -aes-256-cbc '
                        + '-md sha256 -iter 100000 -salt -in "' + currentFile + '" -pass file:"'
                        + plainKeyFile + '" -out "' + encFile + '"');
                }
                
                DeleteTempFile(plainKeyFile);
            }
        }
        
        enumFiles.moveNext();
        progress.StepFiles(1);
    }
}

function CheckConfig() {
    var sslTool = GetSslTool();
    var pkscTool = GetPkscTool();
    var pkscDll = GetPkscDll();
    var pkscId = GetPkscId();
    
    if (!DOpus.FSUtil.Exists(sslTool)) {
        DOpus.Output(Str("error_config_no_file") + sslTool, true);
        return false;
    }
    if (!DOpus.FSUtil.Exists(pkscTool)) {
        DOpus.Output(Str("error_config_no_file") + pkscTool, true);
        return false;
    }
    if (!DOpus.FSUtil.Exists(pkscDll)) {
        DOpus.Output(Str("error_config_no_file") + pkscDll, true);
        return false;
    }
    if (pkscId < 0 || pkscId > 10) {
        DOpus.Output(Str("error_config_invalid_id") + pkscId, true);
        return false;
    }
    return true;
}

function InitProgress(data) {
    var progress = data.func.command.progress;
    progress.abort = true;
    progress.bytes = false;
    progress.delay = true;
    progress.pause = true;
    progress.skip = false;
    progress.full = false;
    progress.Init(data.func.sourcetab);
    progress.SetFiles(data.func.sourcetab.selstats.selfiles);
    progress.SetTitle("YubiKey");
    progress.SetStatus("");
    return progress;
}

function GetSslTool() {
    return Script.config.openssl + "\\bin\\openssl.exe";
}

function GetPkscTool() {
    return Script.config.opensc + "\\tools\\pkcs11-tool.exe";
}

function GetPkscDll() {
    return Script.config.opensc + "\\pkcs11\\opensc-pkcs11.dll";
}

function GetPkscId() {
    return Script.config.pksc_id;
}

function GetTempFile() {
    return DOpus.FSUtil.GetTempFilePath(".tmp","script-yubikey-")
}

function GetRandomTempFile(data) {
    var tempFile = DOpus.FSUtil.GetTempFile(".tmp", "script-yubikey-", "r", data.func.sourcetab);
    tempFile.Write(RandomString(64));
    tempFile.Close();
    return tempFile.path;
}

function RandomString(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}

function DeleteTempFile(path) {
    cmdLine = 'Delete QUIET NORECYCLE FILE="' + path + '"';
    DOpus.Create().Command().RunCommand(cmdLine);
}

function ExecuteSilent(cmd, cmdLine) {
    cmd.SetModifier("runmode", "hide");
    cmd.RunCommand('@sync:' + cmdLine);
    cmd.ClearModifier("runmode");
}

function Execute(cmdLine) {
    return shell.Run(cmdLine,1,true) == 0;
}

function ToEncFile(path) {
    if (path.ext == ".enc") {
        return path;
    }
    if (path.ext == ".key") {
        var encFile = DOpus.FSUtil.NewPath(path.pathpart);
        encFile.Add(path.stem + ".enc");
        if (DOpus.FSUtil.Exists(encFile)) {
            return encFile;
        }
    }
    return null;
}

function ToKeyFile(path) {
    if (path.ext == ".key") {
        return path;
    }
    if (path.ext == ".enc") {
        var keyFile = DOpus.FSUtil.NewPath(path.pathpart);
        keyFile.Add(path.stem + ".key");
        if (DOpus.FSUtil.Exists(keyFile)) {
            return keyFile;
        }
    }
    return null;
}

function FetchPubKeyFromYubiKey(data, sslTool, pkscTool, pkscDll, pkscId) {
    var derCert = GetTempFile();
    ExecuteSilent(data.func.command, '"' + pkscTool + '" --id ' + pkscId
        + ' --read-object --type pubkey -m RSA-PKCS --module "' + pkscDll
        + '" -o "' + derCert + '"');
    if (!DOpus.FSUtil.Exists(derCert) || IsEmptyFile(derCert)) {
        return null;
    }
    
    var pemCert = GetTempFile();
    ExecuteSilent(data.func.command, '"' + sslTool + '" rsa -pubin -inform DER -in "'
        + derCert + '" -outform PEM -out "' + pemCert + '"');
    DeleteTempFile(derCert);
    if (!DOpus.FSUtil.Exists(pemCert) || IsEmptyFile(pemCert)) {
        return null;
    }
    
    return pemCert;
}

function IsEmptyFile(path) {
    return DOpus.FSUtil.GetItem(path).size == "0";
}

function Str(str) {
    return DOpus.strings.Get(str);
}

function Error(data, message) {
    var dlg = data.func.Dlg();
    dlg.icon = "error";
    dlg.message = message;
    dlg.window = data.func.sourcetab;
    dlg.buttons = "&OK";
    dlg.Show();
}

function AskReplace(data, destination) {
    var dlg = data.func.Dlg();
    dlg.icon = "question";
    dlg.message = Str("error_file_exists") + "\n" + destination + "\n" + Str("question_replace");
    dlg.window = data.func.sourcetab;
    dlg.buttons = Str("button_replace") + "|" + Str("button_skip") + "|" + Str("button_cancel");
    return dlg.Show();
}

==SCRIPT RESOURCES

<resources>
    <resource type="strings">
        <strings lang="english">
            <string id="script_name" text="Yubikey files encryption" />
            <string id="script_desc" text="Adds a command to encrypt and decrypt files with use of YubiKey" />
            <string id="cmd_name" text="Encrypt or decrypt selected files" />
            <string id="cmd_desc" text="Encrypt or decrypt selected files using YubiKey PIV" />
            <string id="error_encrypt_fail_cert_read" text="Failed on fetching public key out of YubiKey" />
            <string id="error_encrypt_fail_key" text="Failed on creating .key file in location:" />
            <string id="error_encrypt_dir" text="Cannot encrypt directories" />
            <string id="error_decrypt_dir" text="Cannot decrypt directories" />
            <string id="error_decrypt_no_key" text="Cannot find .key file to decrypt for: " />
            <string id="error_decrypt_no_enc" text="Cannot find .enc file to decrypt for: " />
            <string id="error_decrypt_fail_key" text="Cannot decrypt .key file for: " />
            <string id="error_config_no_file" text="Cannot find: " />
            <string id="error_config_invalid_id" text="Invalid ID configured: " />
            <string id="error_file_exists" text="Destination file already exists in location:" />
            <string id="question_replace" text="Replace it?" />
            <string id="button_replace" text="&Replace" />
            <string id="button_skip" text="&Skip" />
            <string id="button_cancel" text="&Cancel" />
        </strings>
        <strings lang="polski">
            <string id="script_name" text="Szyfrowanie z Yubikey" />
            <string id="script_desc" text="Dodaje komendę do szyfrowania i deszyfrowania plików z użyciem YubiKey" />
            <string id="cmd_name" text="Szyfruj lub deszyfruj wybrane pliki" />
            <string id="cmd_desc" text="Szyfruj lub deszyfruj wybrane pliki z użyciem YubiKey PIV" />
            <string id="error_encrypt_fail_cert_read" text="Nie powiodło się pobieranie klucza publicznego z urządzenia YubiKey" />
            <string id="error_encrypt_fail_key" text="Nie udało się utworzyć pliku .key w lokacji:" />
            <string id="error_encrypt_dir" text="Nie można szyfrować katalogów" />
            <string id="error_decrypt_dir" text="Nie można rozszyfrowywać katalogów" />
            <string id="error_decrypt_no_key" text="Nie znalazłem pliku .key do rozszyfrowania dla: " />
            <string id="error_decrypt_no_enc" text="Nie znalazłem pliku .enc do rozszyfrowania dla: " />
            <string id="error_decrypt_fail_key" text="Nie udało się rozszyfrowanie pliku .key dla: " />
            <string id="error_config_no_file" text="Nie znalazłem: " />
            <string id="error_config_invalid_id" text="Skonfigurowano niepoprawny ID: " />
            <string id="error_file_exists" text="Plik docelowy już istnieje w lokacji:" />
            <string id="question_replace" text="Zastąpić go?" />
            <string id="button_replace" text="&Zastąp" />
            <string id="button_skip" text="P&omiń" />
            <string id="button_cancel" text="&Przerwij" />
        </strings>
    </resource>
</resources>

Sort:  

Bardzo dobry artykuł, fajnie, że wróciłeś z przytupem.
Ja ostatnio chciałem kupić w promocjach, ale cały czas boję się, że go stracę i będzie problem

Wracam-nie wracam. Bywam cały czas. ;-)

Swoje klucze YubiKey kupiłem już jakiś czas temu, jak ceny jeszcze były niższe niż dzisiejsze. Używam do wielu celów regularnie, wtopiły się już w moją normalność. Fajna rzecz, nie jest niezbędna, ale przydatna. Osobiście klucza raczej za dużo nie noszę. Rzadko kiedy potrzebuję rozmaitych haseł/szyfrowania/logowań poza domem.