Instrukcja usługi SIO-KReM PROD
Instrukcja połączenia do usługi sieciowej SIO-KReM na środowisku produkcyjnym
Nazwa: | Wdrożenie Krajowego Systemu Danych OÅ›wiatowych" wspóÅ‚finansowanego ze Å›rodków Europejskiego Funduszu SpoÅ‚ecznego |
Data: | 19.05.2023 r. |
Wersja | 1.1 |
Spis treści
1. System SIO-Krem
2. Wykorzystane technologie i standardy przy tworzeniu serwera usług sieciowych
3. Dodanie klienta usługi sieciowej w aplikacji SIO-KReM
4. Uwierzytelnianie:
5. Podpis elektroniczny:
5.1 Algorytm podpisywania wiadomości
6. Interfejsy:
6.1 GetStudentResultSet
7. Dodatkowe uwagi do zapytań SOAP
8. Przykładowa klasa JAVA do obsługi kluczy RSA SHA512:
9. Przykładowa klasa JAVA do obsługi kluczy OpenPGP:
System SIO-Krem
Aplikacja SIO-KREM jest częściÄ… SIO (Systemu Informacji OÅ›wiatowej) i jest przeznaczona do wsparcia procesu rekrutacji kandydatów na uczelnie wyższe poprzez udostÄ™pnienie wyników egzaminów maturalnych oraz innych zwiÄ…zanych z tym informacji uczelniom, które posiadajÄ… aktywny dostÄ™p do SIO i podadzÄ… PESEL kandydata.
Podstawą prawną działania aplikacji SIO-KREM jest art. 62 ustawy o SIO (pozyskiwanie danych przez uczelnie).
Do bazy danych SIO-KREM majÄ… dostÄ™p uczelnie publiczne, niepubliczne i koÅ›cielne zarejestrowane w bazie danych SIO z typem podmiotu S001 id = 142, 145, 162, które posiadajÄ… aktywny dostÄ™p do SIO.
W bazie danych SIO-KREM znajdujÄ… siÄ™ informacje o wynikach zdanego egzaminu maturalnego poszczególnych osób (w przyszÅ‚oÅ›ci również wyniki egzaminu zawodowego lub egzaminu potwierdzajÄ…cego kwalifikacje w zawodzie). Wyniki egzaminów przekazywane sÄ… przez OKE do SIO-KREM na podstawie art. 20 ustawy o SIO.
Na informacje o kandydacie składają się dane o:
• procentowych wynikach z poszczególnych egzaminów maturalnych
•wskazanie centyla, w którym dany wynik siÄ™ znajduje
•lista numerów dokumentów wydanych danemu kandydatów
•informacjÄ™ o ukoÅ„czonej szkole
Zalogowana uczelnia ma możliwość zarejestrowania PESEL kandydata - wpisanego ręcznie lub zaimportowanego z pliku.
Aplikacja przyjmuje jedynie PESELe o poprawnej strukturze oraz informuje jakie PESELe z załączonego pliku są błędne (tj. mają nieprawidłową strukturę).
Po zarejestrowaniu danych na liÅ›cie kandydatów danej uczelni uczelnia ma możliwość pobrania plików csv z danymi:
•wyników
•numerów dokumentów
• centyli
•szkóÅ‚
Pliki podzielone sÄ… na pliki z:
•nowymi danymi - czyli takie dane, które nigdy nie byÅ‚y pobrane przez dany podmiot z danego zakresu
•aktualizacjami danych - czyli takie dane, które już byÅ‚y pobrane a w miÄ™dzyczasie do bazy danych SIO-KReM wpÅ‚ynęła aktualizacja danych
•wszystkimi danymi - czyli peÅ‚en komplet danych z danego zakresu
•plik z brakami - czyli lista PESELi, dla których nie ma danych
Do bazy danych SIO-KREM zostajÄ… przesÅ‚ane dane maturzystów, którzy w deklaracji przystÄ…pienia do matury wyrazili zgodÄ™ na przekazywanie danych do KReM. W przypadku, gdy kandydat wycofa zgodÄ™ na przekazywanie danych do SIO-KREM w aplikacji zostanÄ… usuniÄ™te dane o wynikach ucznia oraz zostaje zaktualizowany plik z wycofanymi zgodami.
Uczelnie mogÄ… przeglÄ…dać listÄ™ kandydatów i zakÅ‚adać filtry na wyszukiwanie danych oraz pobierać pliki z danymi dla wyszukanej grupy kandydatów.
Aplikacja SIO-KREM prowadzi również rejestr/historiÄ™ wysÅ‚anych i pobranych z SIO-KReM plików. Dane w SIO-KREM sÄ… przechowywane w bazie danych bezterminowo.
Z SIO-KREM można również pobrać sÅ‚ownik arkuszy egzaminacyjnych, sÅ‚ownik szkóÅ‚ oraz sÅ‚ownik olimpiad.
Wykorzystane technologie i standardy przy tworzeniu serwera usług sieciowych
System SIO-KReM udostępnia usługi sieciowe pod adresem https://krem1.sio.gov.pl/ws. Adres pliku WSDL specyfikującego interfejs usług sieciowych to https://krem1.sio.gov.pl/ws?wsdl.
Serwer usług sieciowych został napisany zgodnie ze standardami SOAP (Simple Object Access Protocol). Obsługuje ̨ wersje protokołu SOAP 1.1 [SOAP] i SOAP 1.2 [SOAP1.2].
Do opisu interfejsu został wykorzystany standard WSDL (Web Services Description Language) [WSDL].
CaÅ‚a komunikacja z serwerem usÅ‚ug sieciowych jest szyfrowana za pomocÄ… TLS/SSL (jest jednostronna – adres https://krem1.sio.gov.pl/ws podpisany jest zaufanym certyfikatem) [RFC2246]
Uwierzytelnianie klienta jest przeprowadzane poprzez identyfikator i hasło (ustawiane poprzez interfejs systemu SIO-KReM) zgodnie ze standardem WS-Security [UTP].
Do podpisywania komunikatów użyty zostaÅ‚ standard RSA-SHA512 [RFC8332] i OpenPGP [RFC2440]
Dodanie klienta usługi sieciowej w aplikacji SIO-KReM
Aby skonfigurować połączenie systemu uczelni do usługi SIO-KReM należy zalogować się do aplikacji SIO-KReM dostępnej pod adresem [
https://krem.sio.gov.pl
]
|
Rysunek 1 Okno Konfiguracji połączenia
- Użytkownik w module „Dane systemu" wypeÅ‚nia dane wymagane do poÅ‚Ä…czenia miÄ™dzy usÅ‚ugÄ… SIO-KreM a systemem uczelni (wszystkie pola sÄ… wymagane) tj. :
- ImiÄ™;
- Nazwisko;
- Adres e-mail;
- Numer telefonu;
- Identyfikator/Login
- adres IP - adres serwera z którego bÄ™dzie wysyÅ‚ane zapytanie do usÅ‚ugi SIO-KReM.
- Hasło;
- Powtórzenie hasÅ‚a;
- Klucz publiczny systemu uczelni - wymagany format PEM (zawierajÄ…cy header 'BEGIN PGP PUBLIC KEY BLOCK' lub 'BEGIN PUBLIC KEY' – w zależnoÅ›ci od wybranego rodzaju klucza). Preferujemy klucz RSA od PGP.
Rysunek 2 Dane systemu
Po uzupeÅ‚nieniu wszystkich pól, użytkownik zatwierdza konfiguracjÄ™ przyciskiem „Zapisz". System poinformuje o pomyÅ›lnej konfiguracji.
W oknie po prawej stronie klucza publicznego systemu uczelni wyświetli się wygenerowany klucz publiczny usługi SIO-KReM oraz daty ważności obydwu kluczy.
Widok konfiguracji klucza RSA:
Rysunek 3 Konfiguracja połączenia z kluczem RSA
W przypadku klucza PGP dodatkowo wyświetlą się pola zawierające odciski palca obydwu kluczy.
Widok konfiguracji klucza PGP:
Rysunek 4 Konfiguracja połączenia z kluczem PGP
Dostęp do usługi SIO-KReM (https://krem1.sio.gov.pl/ws ) będzie aktywny maksymalnie po 5 minutach.
Jeżeli użytkownik chce edytować dane poÅ‚Ä…czenia, wybiera „Modyfikuj" umieszczony w prawym górnym rogu.
Rysunek 5 Modyfikacja połączenia
Uwierzytelnianie:
Uwierzytelnianie klientów usÅ‚ug sieciowych odbywa siÄ™ podobnie, jak w przypadku stron internetowych, czyli poprzez identyfikator i hasÅ‚o. Informacje te umieszczane sÄ… wewnÄ…trz nagÅ‚ówka wiadomoÅ›ci SOAP, czyli <Header>, w elemencie <Security>. Identyfikator i hasÅ‚o sÄ… opakowane w element <UsernameToken>, który posiada dwa podelementy: <Username> — zawierajÄ…cy identyfikator klienta oraz <Password> — zawierajÄ…cy hasÅ‚o. Dodatkowo element <Password> może zawierać atrybut Type, który okreÅ›la typ przesyÅ‚anego hasÅ‚a. SÄ… dwie możliwoÅ›ci: PasswordText — hasÅ‚o przesyÅ‚ane jest zwykÅ‚ym tekstem, PasswordDigest — hasÅ‚o przesyÅ‚ane jest w postaci skrótu (ang. digest). JeÅ›li żaden atrybut nie zostaÅ‚ podany, to domyÅ›lnie obowiÄ…zuje typ PasswordText. UsÅ‚uga SIO-KReM akceptuje obie te możliwoÅ›ci, choć zalecane jest przesyÅ‚anie hasÅ‚a w postaci skrótu (w przypadku usÅ‚ugi SIO-KReM musi on zostać wykonany funkcjÄ… MD5). Wszystkie elementy muszÄ… być w przestrzeni nazw WS-Security [UTP].
Oto przykÅ‚ad poprawnego nagÅ‚ówka zawierajÄ…cego identyfikator user i skrót hasÅ‚a password wykonany funkcjÄ… MD5:
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss- wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username> user </wsse:Username> <wsse:Password Type="#PasswordDigest"> c62d929e7b7e7b6165923a5dfc60cb56 </wsse:Password> </wsse:UsernameToken>
Podpis elektroniczny:
W każdej poprawnej wiadomoÅ›ci SOAP przesyÅ‚anej od klienta do serwera lub w odwrotnÄ… stronÄ™, musi się̨ znaleźć podpis elektroniczny caÅ‚ego elementu <Body>. Musi być on umieszczony, wewnÄ…trz nagÅ‚ówka <Security>, w specjalnym elemencie <Signature> (w przypadku użycia prostego klucza RSA) lub <PGPSignature> (w przypadku użycia klucza OpenPGP). Aby utworzyć poprawny podpis elektroniczny zaakceptowany przez system SIO-KReM należy użyć dwóch standardów: Exclusive XML Canonicalization [xml-exc-c14n] oraz jeden ze standardów: RSA-SHA512 [RFC8332] lub OpenPGP [RFC2440].
Pierwszy sÅ‚uży do sprowadzenia dokumentu XML do postaci kanonicznej, natomiast drugi jest używany do wygenerowania podpisu (odÅ‚Ä…czonego, ang. detached) na postaci kanonicznej odpowiedniej struktury XML (w naszym przypadku elementu <Body>). Konieczność użycia algorytmu sprowadzania do postaci kanonicznej wynika z faktu, że dwa logicznie takie same dokumenty XML mogą̨ się̨ trochÄ™ różnić tekstowo (np. innÄ… kolejnoÅ›ciÄ… atrybutów, niewypisywaniem atrybutów domyÅ›lnych, nadmiarowymi spacjami, sposobem umieszczania informacji o przestrzeniach nazw, różnym traktowaniem znaku koÅ„ca wiersza itp.) i wtedy mogÅ‚oby dojść do niezamierzonej, niepoprawnej weryfikacji podpisu. Dlatego by być pewnym, że do podpisu i weryfikacji bÄ™dziemy używali tego samego dokumentu, konieczne jest przed wykonaniem tych operacji, przeksztaÅ‚cenie dokumentu XML do postaci kanonicznej. Algorytm ten jest elementem standardu XML Signature [XMLDSig]
Algorytm podpisywania wiadomości
Poniżej został przedstawiony dokładny algorytm podpisywania wiadomości wymienianych z serwerem usług sieciowych systemu SIO-KReM:
- Identyfikacja elementu <Body> w dokumencie,
- Wykonanie algorytmu kanonizacji na elemencie <Body> wraz zawartością,
- Stworzenie podpisu odłączonego na wyniku kanonizacji (z użyciem RSA SHA512),
Zakodowanie wartości podpisu w postaci Base64 [RFC2045],
- Stworzenie wewnÄ…trz nagÅ‚ówka <Security> (zgodnego ze standardem WS-Security) pod-elementu <Signature> (lub <PGPSignature> w przypadku OpenPGP), który bÄ™dzie zawieraÅ‚ wartość podpisu (w postaci Base64),
- Umieszczenie wewnątrz elementu <Signature> (lub <PGPSignature> w przypadku OpenPGP) wartości wcześniej wygenerowanego podpisu.
Proces weryfikacji wygląda następująco:
- Identyfikacja nagÅ‚ówka <Security> wraz z podelementem <Signature> (lub <PGPSignature> w przypadku OpenPGP),
- Identyfikacja elementu <Body> w dokumencie,
- Wykonanie algorytmu kanonizacji na elemencie <Body> wraz zawartością,
- Odkodowanie wartości podpisu umieszczonego wewnątrz elementu <Signature> z postaci Base64 do binarnej,
- Weryfikacja czy wartość podpisu (w formacie binarnym) odpowiada podpisowi skanonizowanej postaci elementu <Body> stworzonego przez nadawcę.
Każde zapytanie wysyÅ‚ane do serwera usÅ‚ug sieciowych systemu SIO-KReM musi zostać podpisane kluczem prywatnym administratora (Uczelni), który jest zarejestrowany w systemie SIO-KREM.
Każda odpowiedź odsyłana przez usługę SIO-KReM jest podpisywana kluczem prywatnym systemu (klucz publiczny usługi do weryfikacji wiadomości udostępniony jest w systemie SIO-KREM w zakładce administracyjnej systemu uczelni).
Podany kod jest przykÅ‚adem poprawnie podpisanego pliku. Jest to zapytanie dla funkcji GetStudentResultSet podpisane omówionym algorytmem (odpowiednio sformatowane by byÅ‚e bardziej czytelne). Zapytanie zawiera także nagÅ‚ówek autoryzacyjny, w którym identyfikatorem administratora jest user, a hasÅ‚em password.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username>user</wsse:Username> <wsse:Password>password</wsse:Password> </wsse:UsernameToken> <siokrem:Signature xmlns:siokrem="https://krem1.sio.gov.pl"> iEYEABE...hsv7y </siokrem:Signature> </wsse:Security> </S:Header> <S:Body> <GetStudentResultSet xmlns="https://krem1.sio.gov.pl "> <pesel>86091172941</pesel> </GetStudentResultSet> </S:Body> </S:Envelope>
Interfejsy:
GetStudentResultSet
Pobranie wyników wielu maturzystów.
Parametry — lista struktura zawierajÄ…cych numery PESEL maturzystów.
Wynik — ogólny kod zakoÅ„czenia funkcji (kod sukcesu lub informacja o bÅ‚Ä™dzie) i lista struktur (dokÅ‚adnie tyle ile numerów PESEL byÅ‚o w zapytaniu) zawierajÄ…ca wynik wykonania funkcji (kod sukcesu lub bÅ‚Ä™du) i oceny maturzysty (w przypadku gdy ocen nie ma w bazie lub zapytanie dla tego maturzysty zakoÅ„czyÅ‚o siÄ™ innym bÅ‚Ä™dem, zbiór ocen jest pusty).
Przykładowe zapytanie:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://krem1.sio.gov.pl"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username>user_uni</wsse:Username> <wsse:Password Type="#PasswordDigest"> 5f4dcc3b5aa765d61d8327deb882cf99 </wsse:Password> </wsse:UsernameToken> <ns1:Signature> iD8DBQBG...HVMhBkJ8xI= </ns1:Signature> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <ns1:GetStudentResultSet> <ns1:pesel>11111111111</ns1:pesel> <ns1:pesel>88121212124</ns1:pesel> <ns1:pesel>88122408083</ns1:pesel> </ns1:GetStudentResultSet> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Przykładowa odpowiedź:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://krem.uw.edu.pl"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1"> <ns1:PGPSignature> iD8DBQBG...JxsOT8= </ns1:PGPSignature> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <ns1:GetStudentResultSetResponse> <ns1:responseCode>0</ns1:responseCode> <ns1:response> <ns1:errorCode>12</ns1:errorCode> <ns1:studentResult> <ns1:pesel>11111111111</ns1:pesel> <ns1:results/> </ns1:studentResult> </ns1:response> <ns1:response> <ns1:errorCode>11</ns1:errorCode> <ns1:studentResult> <ns1:pesel>88121212124</ns1:pesel> <ns1:results/> </ns1:studentResult> </ns1:response> <ns1:response> <ns1:errorCode>0</ns1:errorCode> <ns1:studentResult> <ns1:pesel>88122408083</ns1:pesel> <ns1:results> <ns1:exam> <ns1:code>PO_u_N_p_082</ns1:code> <ns1:points>30</ns1:points> </ns1:exam> <ns1:exam> <ns1:code>PO_p_P_p_082</ns1:code> <ns1:points>40</ns1:points> </ns1:exam> <ns1:exam> <ns1:code>PO_p_R_p_082</ns1:code> <ns1:points>30</ns1:points> </ns1:exam> </ns1:results> </ns1:studentResult> </ns1:response> </ns1:GetStudentResultSetResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Kody błędu (errorCode):
('RESULT_OK',0) ('RESULT_ERROR_WRONG_PESEL_NO',12) ('RESULT_ERROR_NO_DATA',11) ('RESULT_ERROR_WRONG_MARK',25) ('RESULT_PESEL_WITHDRAWN',41) ('RESULT_UNKNOWN_PESEL',43)
Dodatkowe uwagi do zapytań SOAP
Ze wzglÄ™dów wydajnoÅ›ciowych, w usÅ‚udze SIO-KReM bÄ™dzie ustalony limit — okreÅ›lajÄ…cy ile danych różnych maturzystów może znajdować siÄ™ w jednym zapytaniu (i odpowiedzi SOAP). Obecnie limit wynosi 1000, czyli np. zapytanie dodajÄ…ce 800 wyników maturzystów wykona sį prawidÅ‚owo, natomiast proÅ›ba o pobranie 2000 maturzystów zakoÅ„czy siÄ™ bÅ‚Ä™dem. Gdy klient chce wykonać operacje dla wiÄ™kszej iloÅ›ci danych, to musi wykonać kilka razy tÄ™ samÄ… funkcjÄ™, w każdym wywoÅ‚aniu podajÄ…c inne parametry.
Przykładowa klasa JAVA do obsługi kluczy RSA SHA512:
package pl.men.utils.common;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Cryptography {
private static final String PUBLIC_KEY_PATH = "sciezka_do_pliku.pub";
private static final String PRIVATE_KEY_PATH = "sciezka_do_pliku.key";
private static final String SIG_ALG = "SHA512withRSA";
private static final String KEY_ALG = "HmacSHA512";
<span style="color: #7f0055"><strong>public</strong></span> <span style="color: #7f0055"><strong>static</strong></span> <span style="color: #7f0055"><strong>byte</strong></span>[] generateSignature (String <span style="color: #6a3e3e">content</span>) <span style="color: #7f0055"><strong>throws</strong></span> NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException, IOException {
Signature <span style="color: #6a3e3e">sign</span> = Signature.getInstance(<span style="color: #0000c0"><strong><em>SIG_ALG</em></strong></span>);
PrivateKey <span style="color: #6a3e3e">privateKey</span> = readPrivateKeyFromFile();
<span style="color: #6a3e3e">sign</span>.initSign(<span style="color: #6a3e3e">privateKey</span>);
<span style="color: #7f0055"><strong>byte</strong></span>[] <span style="color: #6a3e3e">bytes</span> = <span style="color: #6a3e3e">content</span>.getBytes();
<span style="color: #6a3e3e">sign</span>.update(<span style="color: #6a3e3e">bytes</span>);
<span style="color: #7f0055"><strong>return</strong></span> <span style="color: #6a3e3e">sign</span>.sign();
}
<span style="color: #7f0055"><strong>public</strong></span> <span style="color: #7f0055"><strong>static</strong></span> <span style="color: #7f0055"><strong>boolean</strong></span> verifySignature (String <span style="color: #6a3e3e">publicKey</span>, String <span style="color: #6a3e3e">content</span>, <span style="color: #7f0055"><strong>byte</strong></span>[] <span style="color: #6a3e3e">signature</span>) <span style="color: #7f0055"><strong>throws</strong></span> NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException, IOException {
Signature <span style="color: #6a3e3e">sign</span> = Signature.getInstance(<span style="color: #0000c0"><strong><em>SIG_ALG</em></strong></span>);
<span style="color: #6a3e3e">sign</span>.initVerify(readPublicKeyFromString(<span style="color: #6a3e3e">publicKey</span>));
<span style="color: #6a3e3e">sign</span>.update(<span style="color: #6a3e3e">content</span>.getBytes());
<span style="color: #7f0055"><strong>return</strong></span> <span style="color: #6a3e3e">sign</span>.verify(<span style="color: #6a3e3e">signature</span>);
}
<span style="color: #7f0055"><strong>public</strong></span> <span style="color: #7f0055"><strong>static</strong></span> <span style="color: #7f0055"><strong>void</strong></span> generateKeyPair() <span style="color: #7f0055"><strong>throws</strong></span> NoSuchAlgorithmException, IOException {
KeyPairGenerator <span style="color: #6a3e3e">keyPairGen</span> = KeyPairGenerator.getInstance(<span style="color: #2a00ff">"RSA"</span>);
<span style="color: #6a3e3e">keyPairGen</span>.initialize(2048);
KeyPair <span style="color: #6a3e3e">pair</span> = <span style="color: #6a3e3e">keyPairGen</span>.generateKeyPair();
saveKeyPairToFiles(<span style="color: #6a3e3e">pair</span>);
}
<span style="color: #7f0055"><strong>private</strong></span> <span style="color: #7f0055"><strong>static</strong></span> PublicKey readPublicKeyFromString(String <span style="color: #6a3e3e">publicKey</span>) <span style="color: #7f0055"><strong>throws</strong></span> IOException, NoSuchAlgorithmException, InvalidKeySpecException {
<span style="color: #6a3e3e">publicKey</span> = <span style="color: #6a3e3e">publicKey</span>.replace(<span style="color: #2a00ff">"---<span style="text-decoration: line-through; ">BEGIN PUBLIC KEY</span>--"</span>, <span style="color: #2a00ff">""</span>).replace(<span style="color: #2a00ff">"--<span style="text-decoration: line-through; ">END PUBLIC KEY</span>---"</span>, <span style="color: #2a00ff">""</span>).replaceAll(System.lineSeparator(), <span style="color: #2a00ff">""</span>).replaceAll(<span style="color: #2a00ff">" "</span>, <span style="color: #2a00ff">""</span>).replaceAll(<span style="color: #2a00ff">"<br class="atl-forced-newline" />s+"</span>,<span style="color: #2a00ff">""</span>);
<span style="color: #7f0055"><strong>byte</strong></span>[] <span style="color: #6a3e3e">publicKeyBytes</span> = Base64.getDecoder().decode(<span style="color: #6a3e3e">publicKey</span>);
KeyFactory <span style="color: #6a3e3e">keyFactory</span> = KeyFactory.getInstance(<span style="color: #2a00ff">"RSA"</span>);
EncodedKeySpec <span style="color: #6a3e3e">publicKeySpec</span> = <span style="color: #7f0055"><strong>new</strong></span> X509EncodedKeySpec(<span style="color: #6a3e3e">publicKeyBytes</span>);
<span style="color: #7f0055"><strong>return</strong></span> <span style="color: #6a3e3e">keyFactory</span>.generatePublic(<span style="color: #6a3e3e">publicKeySpec</span>);
}
<span style="color: #7f0055"><strong>private</strong></span> <span style="color: #7f0055"><strong>static</strong></span> PublicKey readPublicKeyFromFile() <span style="color: #7f0055"><strong><span style="text-decoration: underline; ">throws</span></strong></span> IOException, NoSuchAlgorithmException, InvalidKeySpecException {
File <span style="color: #6a3e3e">file</span> = <span style="color: #7f0055"><strong>new</strong></span> File(<span style="color: #0000c0"><strong><em>PUBLIC_KEY_PATH</em></strong></span>);
DataInputStream <span style="color: #6a3e3e">dis</span> = <span style="color: #7f0055"><strong>new</strong></span> DataInputStream(<span style="color: #7f0055"><strong>new</strong></span> FileInputStream(<span style="color: #6a3e3e">file</span>));
<span style="color: #7f0055"><strong>byte</strong></span>[] <span style="color: #6a3e3e">pubKeyBytes</span> = <span style="color: #7f0055"><strong>new</strong></span> <span style="color: #7f0055"><strong>byte</strong></span>[(<span style="color: #7f0055"><strong>int</strong></span>) <span style="color: #6a3e3e">file</span>.length()];
<span style="color: #6a3e3e">dis</span>.read(<span style="color: #6a3e3e">pubKeyBytes</span>);
<span style="color: #6a3e3e">dis</span>.close();
KeyFactory <span style="color: #6a3e3e">factory</span> = KeyFactory.getInstance(<span style="color: #2a00ff">"RSA"</span>);
EncodedKeySpec <span style="color: #6a3e3e">publicKeySpec</span> = <span style="color: #7f0055"><strong>new</strong></span> X509EncodedKeySpec(<span style="color: #6a3e3e">pubKeyBytes</span>);
<span style="color: #7f0055"><strong>return</strong></span> <span style="color: #6a3e3e">factory</span>.generatePublic(<span style="color: #6a3e3e">publicKeySpec</span>);
}
<span style="color: #7f0055"><strong>private</strong></span> <span style="color: #7f0055"><strong>static</strong></span> PrivateKey readPrivateKeyFromFile() <span style="color: #7f0055"><strong>throws</strong></span> IOException, NoSuchAlgorithmException, InvalidKeySpecException {
File <span style="color: #6a3e3e">file</span> = <span style="color: #7f0055"><strong>new</strong></span> File(<span style="color: #0000c0"><strong><em>PRIVATE_KEY_PATH</em></strong></span>);
DataInputStream <span style="color: #6a3e3e">dis</span> = <span style="color: #7f0055"><strong>new</strong></span> DataInputStream(<span style="color: #7f0055"><strong>new</strong></span> FileInputStream(<span style="color: #6a3e3e">file</span>));
<span style="color: #7f0055"><strong>byte</strong></span>[] <span style="color: #6a3e3e">privKeyBytes</span> = <span style="color: #7f0055"><strong>new</strong></span> <span style="color: #7f0055"><strong>byte</strong></span>[(<span style="color: #7f0055"><strong>int</strong></span>) <span style="color: #6a3e3e">file</span>.length()];
<span style="color: #6a3e3e">dis</span>.read(<span style="color: #6a3e3e">privKeyBytes</span>);
<span style="color: #6a3e3e">dis</span>.close();
KeyFactory <span style="color: #6a3e3e">factory</span> = KeyFactory.getInstance(<span style="color: #2a00ff">"RSA"</span>);
PKCS8EncodedKeySpec <span style="color: #6a3e3e">privKeySpec</span> = <span style="color: #7f0055"><strong>new</strong></span> PKCS8EncodedKeySpec(<span style="color: #6a3e3e">privKeyBytes</span>);
<span style="color: #7f0055"><strong>return</strong></span> (RSAPrivateKey) <span style="color: #6a3e3e">factory</span>.generatePrivate(<span style="color: #6a3e3e">privKeySpec</span>);
}
<span style="color: #7f0055"><strong>private</strong></span> <span style="color: #7f0055"><strong>static</strong></span> <span style="color: #7f0055"><strong>void</strong></span> saveKeyPairToFiles(KeyPair <span style="color: #6a3e3e">pair</span>) <span style="color: #7f0055"><strong>throws</strong></span> IOException {
FileOutputStream <span style="color: #6a3e3e">fos</span> = <span style="color: #7f0055"><strong>new</strong></span> FileOutputStream(<span style="color: #0000c0"><strong><em>PUBLIC_KEY_PATH</em></strong></span>);
<span style="color: #6a3e3e">fos</span>.write(<span style="color: #6a3e3e">pair</span>.getPublic().getEncoded());
<span style="color: #6a3e3e">fos</span>.close();
FileOutputStream <span style="color: #6a3e3e">out</span> = <span style="color: #7f0055"><strong>new</strong></span> FileOutputStream(<span style="color: #0000c0"><strong><em>PRIVATE_KEY_PATH</em></strong></span>);
<span style="color: #6a3e3e">out</span>.write(<span style="color: #6a3e3e">pair</span>.getPrivate().getEncoded());
<span style="color: #6a3e3e">out</span>.close();
}
<span style="color: #7f0055"><strong>public</strong></span> <span style="color: #7f0055"><strong>static</strong></span> String mapPublicKeyToString(PublicKey <span style="color: #6a3e3e">key</span>) {
StringWriter <span style="color: #6a3e3e">sw</span> = <span style="color: #7f0055"><strong>new</strong></span> StringWriter();
JcaPEMWriter <span style="color: #6a3e3e">writer</span> = <span style="color: #7f0055"><strong>new</strong></span> JcaPEMWriter(<span style="color: #6a3e3e">sw</span>);
<span style="color: #7f0055"><strong>try</strong></span> {
<span style="color: #6a3e3e">writer</span>.writeObject(<span style="color: #6a3e3e">key</span>);
<span style="color: #6a3e3e">writer</span>.close();
} <span style="color: #7f0055"><strong>catch</strong></span> (IOException <span style="color: #6a3e3e">e</span>) {
<span style="color: #6a3e3e">e</span>.printStackTrace();
}
<span style="color: #7f0055"><strong>return</strong></span> <span style="color: #6a3e3e">sw</span>.getBuffer().toString();
}
public static String getStringHash(String message, String secret)
throws InvalidKeyException, NoSuchAlgorithmException {
Mac hmacSHA512 = Mac.getInstance(KEY_ALG);
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), KEY_ALG);
hmacSHA512.init(secretKey);
return org.apache.commons.codec.binary.Base64.encodeBase64String(hmacSHA512.doFinal(message.getBytes()));
}
}
Przykładowa klasa JAVA do obsługi kluczy OpenPGP:
package pl.men.utils.common; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.*; import org.bouncycastle.util.encoders.Hex; import java.io.*; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.util.Iterator; public class Cryptography { private static final String PGP_SECRET_KEY_RING_PATH = "keys/secring.gpg"; private static final String PGP_PROVIDER = "BC"; private static final int PGP_BUFFER_SIZE = 1024; public static PGPSecretKey readPGPSecretKey(InputStream privateKeyRingInputStream, String publicKeyFingerprint) throws IOException, PGPException { Security.addProvider(new BouncyCastleProvider()); PGPUtil.setDefaultProvider(PGP_PROVIDER); privateKeyRingInputStream = PGPUtil.getDecoderStream(privateKeyRingInputStream); PGPSecretKeyRingCollection secretRingCollection = new PGPSecretKeyRingCollection(privateKeyRingInputStream); PGPSecretKey secretKey = null; Iterator<PGPSecretKeyRing> secretKeyRingCollectionIterator = secretRingCollection.getKeyRings(); while (secretKey == null && secretKeyRingCollectionIterator.hasNext()) { PGPSecretKeyRing secretKeyRing = secretKeyRingCollectionIterator.next(); Iterator<PGPSecretKey> secretKeyRingIterator = secretKeyRing.getSecretKeys(); while (secretKey == null && secretKeyRingIterator.hasNext()) { PGPSecretKey currentSecretKey = secretKeyRingIterator.next(); PGPPublicKey currentPublicKey = currentSecretKey.getPublicKey(); String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint())); if (currentSecretKey.isSigningKey() && keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) { secretKey = currentSecretKey; } } } if (secretKey == null) { throw new IllegalArgumentException("Can't find signing key with fingerprint: (" + publicKeyFingerprint + ") in key ring."); } return secretKey; } public static PGPPublicKey readPGPPublicKey(InputStream publicKeyRingInputStream, String publicKeyFingerprint) throws IOException, PGPException { Security.addProvider(new BouncyCastleProvider()); PGPUtil.setDefaultProvider(PGP_PROVIDER); PGPPublicKey publicKey = null; PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyRingInputStream)); Iterator<PGPPublicKeyRing> publicKeyRingIterator = publicKeyRingCollection.getKeyRings(); while (publicKey == null && publicKeyRingIterator.hasNext()) { PGPPublicKeyRing publicKeyRing = publicKeyRingIterator.next(); Iterator<PGPPublicKey> publicKeyIterator = publicKeyRing.getPublicKeys(); while (publicKey == null && publicKeyIterator.hasNext()) { PGPPublicKey currentPublicKey = publicKeyIterator.next(); String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint())); if (keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) { publicKey = currentPublicKey; } } } if (publicKey == null) { throw new IllegalArgumentException("Can't find signing key with fingerprint: (" + publicKeyFingerprint + ") in public key ring."); } return publicKey; } public static PGPPublicKey readPGPPublicKey(String armoredKeyString, String publicKeyFingerprint) throws IOException, PGPException { Security.addProvider(new BouncyCastleProvider()); PGPUtil.setDefaultProvider(PGP_PROVIDER); PGPPublicKey publicKey = null; PGPPublicKeyRingCollection publicKeyRingCollection; try (InputStream publicKeyRingInputStream = new ByteArrayInputStream(armoredKeyString.getBytes())) { publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyRingInputStream)); } Iterator<PGPPublicKeyRing> publicKeyRingIterator = publicKeyRingCollection.getKeyRings(); while (publicKey == null && publicKeyRingIterator.hasNext()) { PGPPublicKeyRing publicKeyRing = publicKeyRingIterator.next(); Iterator<PGPPublicKey> publicKeyIterator = publicKeyRing.getPublicKeys(); while (publicKey == null && publicKeyIterator.hasNext()) { PGPPublicKey currentPublicKey = publicKeyIterator.next(); String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint())); if (keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) { publicKey = currentPublicKey; } } } return publicKey; }
public static byte[]
generatePGPSignature(String content, String publicKeyFingerprint, String secretKeyPassword) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException { String propPath = System.getenv("environment.MulePropPath"); FileInputStream secretKeyRingInputStream= new FileInputStream(propPath + PGP_SECRET_KEY_RING_PATH); ByteArrayInputStream dataInputStream = new ByteArrayInputStream(content.getBytes()); ByteArrayOutputStream signatureOutputStream = new ByteArrayOutputStream(); PGPSecretKey mySecretKey = readPGPSecretKey(secretKeyRingInputStream, publicKeyFingerprint); PGPPrivateKey myPrivateKey = mySecretKey.extractPrivateKey(secretKeyPassword.toCharArray(), PGP_PROVIDER); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(mySecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1, PGP_PROVIDER); signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, myPrivateKey); BCPGOutputStream bcpgOutputStream = new BCPGOutputStream(signatureOutputStream); int bufferSize = PGP_BUFFER_SIZE;
byte[]
bytes = new byte[bufferSize]; int length; while ((length = dataInputStream.read(bytes,0,bufferSize)) > 0) { signatureGenerator.update(bytes,0,length); } signatureGenerator.generate().encode(bcpgOutputStream); signatureOutputStream.close(); return signatureOutputStream.toByteArray(); } public static boolean verifyPGPSignature(String publicKeyString, String publicKeyFingerprint, String content,
byte[]
signatureByteArray) throws Exception { ByteArrayInputStream dataInputStream = new ByteArrayInputStream(content.getBytes()); ByteArrayInputStream signatureInputStream = new ByteArrayInputStream(signatureByteArray); PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(signatureInputStream)); PGPSignatureList signatureList; Object object = factory.nextObject(); if (object instanceof PGPCompressedData) { PGPCompressedData compressedData = (PGPCompressedData) object; factory = new PGPObjectFactory(compressedData.getDataStream()); signatureList = (PGPSignatureList) factory.nextObject(); } else { signatureList = (PGPSignatureList) object; PGPSignature signature = signatureList.get(0); PGPPublicKey publicKey = readPGPPublicKey(publicKeyString, publicKeyFingerprint); signature.initVerify(publicKey, PGP_PROVIDER); int bufferSize = PGP_BUFFER_SIZE;
byte[]
bytes = new byte[bufferSize]; int length; while ((length = dataInputStream.read(bytes,0,bufferSize)) > 0) { signature.update(bytes,0,length); } return signature.verify(); } }