SMS OTP i 2FA z własnego telefonu: kiedy to działa, a kiedy lepiej wybrać inną metodę
Czy SMS jest bezpieczną metodą 2FA? Uczciwy przewodnik: ryzyko SIM swap, wytyczne NIST, kiedy kody SMS z własnego numeru wystarczą i przykład kodu w Pythonie.

Kod SMS przy logowaniu zna każdy, więc łatwo uznać, że wystarczy wysłać wiadomość z numerem. Ten przewodnik jest dla osób, które budują małą aplikację, panel dla klientów albo narzędzie wewnętrzne i zastanawiają się, czy SMS OTP z własnego telefonu to dobry pomysł. Odpowiedź brzmi: czasem tak, ale z wyraźnymi ograniczeniami, które opisuję poniżej razem z działającym przykładem kodu.
Czy SMS jest bezpieczną metodą 2FA?
SMS jako drugi składnik jest wyraźnie lepszy niż samo hasło, ale słabszy niż aplikacja TOTP albo klucz dostępu. Kod podróżuje przez sieć operatora i trafia na numer telefonu, a nie na konkretne urządzenie, więc jego bezpieczeństwo zależy od tego, kto kontroluje numer.
Główne zagrożenia:
- SIM swap: oszust przekonuje operatora do przeniesienia numeru na swoją kartę i od tej chwili dostaje Twoje SMS-y.
- Przeniesienie numeru do innego operatora bez wiedzy właściciela.
- Phishing w czasie rzeczywistym: użytkownik wpisuje kod na fałszywej stronie, a atakujący od razu używa go na prawdziwej.
- Złośliwe aplikacje z dostępem do SMS-ów na telefonie ofiary.
Podobnie ocenia to NIST. W wytycznych SP 800-63B-4 używanie sieci telefonicznej (PSTN) do uwierzytelniania poza kanałem, czyli także SMS-ów, jest jedyną metodą oznaczoną jako ograniczona (restricted). Kto ją stosuje, powinien zaakceptować ryzyko, zaoferować co najmniej jedną nieograniczoną alternatywę, uprzedzić użytkowników o ryzyku i mieć plan migracji. NIST wskazuje też, by przed wysłaniem kodu brać pod uwagę sygnały takie jak niedawna zmiana karty SIM.
Uwaga: ten artykuł nie jest poradą prawną ani audytem bezpieczeństwa. Jeśli chronisz dane finansowe, zdrowotne lub dostęp uprzywilejowany, wybierz metodę odporną na phishing.
Kiedy własny telefon wystarcza do kodów SMS?
Własny telefon jako nadajnik pasuje do małej skali, gdzie SMS jest wygodą, a nie jedyną barierą. Sprawdza się w narzędziach wewnętrznych, panelach dla kilkudziesięciu klientów, potwierdzaniu zamówień telefonicznych i logowaniu pracowników, którzy i tak mają drugi składnik.
| Sytuacja | Czy SMS z własnego numeru wystarczy? |
|---|---|
| Narzędzie wewnętrzne dla zespołu (do kilkudziesięciu osób) | Tak, jako jedna z opcji |
| Panel dla klientów małej firmy, kilkadziesiąt logowań dziennie | Tak, z krótką ważnością kodu i limitami |
| Potwierdzenie zamówienia lub wizyty jednorazowym kodem | Tak |
| Serwis konsumencki z tysiącami logowań | Nie, użyj hurtowej bramki lub innej metody |
| Bankowość, dane zdrowotne, konta administratorów | Nie, użyj kluczy dostępu lub TOTP |
Powody, dla których to nie skaluje się na tysiące użytkowników:
- Telefon wysyła jedną wiadomość co 5 sekund (domyślnie), czyli około 12 na minutę. Kody dla wielu osób naraz ustawią się w kolejce.
- Telefon musi mieć zasilanie, zasięg i internet. To jeden punkt awarii.
- Operatorzy mogą ograniczać masową wysyłkę z kart abonamentowych.
Dlatego nie traktuj tej metody jako zamiennika dedykowanej usługi do logowania konsumentów.
Dlaczego kolejka 72 godzin to wada przy kodach OTP?
Gdy telefon jest wyłączony lub bez zasięgu, wiadomości czekają w kolejce do 72 godzin, a potem wygasają. Przy zwykłych powiadomieniach to zaleta, ale przy kodzie jednorazowym to wada: kod mógłby dotrzeć po godzinie, gdy użytkownik dawno się poddał albo wygenerował nowy.
Rozwiązanie jest po stronie Twojej aplikacji:
- ustaw krótką ważność kodu, np. 5 minut (NIST wymaga, by uwierzytelnienie poza kanałem było nieważne po 10 minutach),
- po wygaśnięciu kodu wymagaj wygenerowania nowego,
- pokaż użytkownikowi, że wysyłka mogła się opóźnić, i daj przycisk „wyślij ponownie”,
- nasłuchuj zdarzeń
MESSAGE_FAILEDiMESSAGE_DELIVERED, by wiedzieć, co się stało (patrz odbieranie SMS przez webhook).
Stan telefonu sprawdzisz w aplikacji na ekranie kondycji urządzenia, który ostrzega m.in. o oszczędzaniu baterii mogącym wstrzymać wysyłkę. Więcej o utrzymaniu telefonu przy życiu znajdziesz na stronie o niezawodności.
Jak zbudować SMS OTP krok po kroku?
Schemat jest prosty: wygeneruj kod, zapisz jego skrót z terminem ważności, wyślij SMS przez API, a przy weryfikacji porównaj skrót i policz próby. Nigdy nie zapisuj samego kodu w bazie.
- Wygeneruj losowy 6-cyfrowy kod generatorem kryptograficznym (nie
random). - Zapisz skrót kodu (HMAC z tajnym kluczem serwera), termin ważności i licznik prób.
- Wyślij SMS przez
POST /gateway/send-sms. - Przy weryfikacji sprawdź termin, limit prób i skrót, a po sukcesie usuń rekord.
import hashlib
import hmac
import os
import secrets
import time
import requests
API_URL = "https://smsportal.app/api/v1/gateway/send-sms"
API_KEY = os.environ["SMSPORTAL_API_KEY"]
PEPPER = os.environ["OTP_PEPPER"].encode() # tajny klucz serwera
TTL_SECONDS = 300 # kod ważny 5 minut
MAX_ATTEMPTS = 5
def _digest(phone: str, code: str) -> str:
return hmac.new(PEPPER, f"{phone}:{code}".encode(), hashlib.sha256).hexdigest()
def send_code(phone: str, store: dict) -> None:
code = f"{secrets.randbelow(10**6):06d}"
store[phone] = {
"digest": _digest(phone, code),
"expires": time.time() + TTL_SECONDS,
"attempts": 0,
}
res = requests.post(
API_URL,
headers={"x-api-key": API_KEY},
json={
"recipients": [phone],
"message": f"Twój kod logowania: {code}. Ważny 5 minut. Nie podawaj go nikomu.",
},
timeout=10,
)
res.raise_for_status()
def verify_code(phone: str, code: str, store: dict) -> bool:
record = store.get(phone)
if not record or time.time() > record["expires"] or record["attempts"] >= MAX_ATTEMPTS:
return False
store[phone] = {**record, "attempts": record["attempts"] + 1}
if hmac.compare_digest(record["digest"], _digest(phone, code)):
del store[phone] # kod jednorazowy
return True
return FalseW produkcji store to Redis z wygasaniem kluczy albo tabela w bazie. Numer podawaj w formacie międzynarodowym (+48...). Polskie znaki w treści przełączają SMS na kodowanie UCS-2 (70 znaków na część), ale powyższa wiadomość i tak mieści się w jednej części.
Kilka zasad, które robią różnicę:
- Limit wysyłek na numer i na adres IP, np. jeden kod na minutę i kilka na godzinę. Bez tego ktoś może zalać cudzy telefon wiadomościami albo wyczerpać Twój pakiet i kolejkę telefonu.
- Limit prób weryfikacji (tu 5), potem wymagaj nowego kodu.
- Jednakowa odpowiedź niezależnie od tego, czy konto istnieje, żeby nie ujawniać użytkowników.
- Treść bez nazw sesji i linków. Kod, nazwa serwisu i ostrzeżenie wystarczą.
Jak wysłać pierwszą wiadomość i skonfigurować klucz, opisuje przewodnik po SMS API.
Czym zastąpić lub uzupełnić SMS?
Jeśli możesz, daj użytkownikom lepszą opcję i traktuj SMS jako zapasową.
| Metoda | Odporność na SIM swap | Odporność na phishing | Uwagi |
|---|---|---|---|
| Kod SMS | niska | niska | wygodny, bez instalacji |
| Aplikacja TOTP (RFC 6238) | wysoka | niska | działa offline, tani |
| Klucze dostępu (passkeys) | wysoka | wysoka | najlepsza ochrona i wygoda |
Rozsądny układ dla małej aplikacji: klucze dostępu lub TOTP jako domyślne, SMS jako opcja dla tych, którzy nie mogą użyć niczego innego, z krótką ważnością kodu i limitami. Przy kodach marketingowych i zgodach pamiętaj o osobnych zasadach: SMS a RODO i zgoda marketingowa. Zauważ też, że wiadomość z własnego numeru nie ma nazwy nadawcy, co omawia artykuł nazwa nadawcy czy własny numer.
Jak sprawdzić, czy to się sprawdzi u Ciebie?
Zanim udostępnisz SMS OTP prawdziwym użytkownikom, zrób krótki test na własnym telefonie:
- Wyślij 20 kodów w odstępach kilku sekund i zmierz, ile trwa dostarczenie każdego.
- Wyłącz telefon na kilka minut i sprawdź, co zobaczy użytkownik. Czy może poprosić o nowy kod?
- Wpisz błędny kod sześć razy i upewnij się, że limit prób działa.
- Poproś o kod dwa razy pod rząd i sprawdź, czy ogranicznik wysyłek zadziałał.
- Sprawdź, ile logowań dziennie realnie masz. Jeśli to setki na godzinę, SMS z własnego telefonu nie wystarczy.
Ten ostatni punkt jest najuczciwszym kryterium: telefon jest świetny dla małej skali i kiepski dla dużej, a granicę poznasz po liczbach, nie po przeczuciu.
Jak zacząć
Jeśli Twoja aplikacja jest mała lub wewnętrzna, wypróbuj to za darmo: załóż konto, połącz telefon i wyślij testowy kod, a potem sprawdź czas dostarczenia i zachowanie przy wyłączonym telefonie. Szczegóły zapytań są w dokumentacji API, a plany w sekcji cennik. Jeśli zobaczysz, że kolejka telefonu nie wystarcza, to znak, że czas na hurtową bramkę lub metodę bez SMS-ów.
Najczęściej zadawane pytania
Czy SMS jest bezpieczną metodą 2FA?
Jest bezpieczniejszy niż samo hasło, ale nie należy do najmocniejszych metod. Kod z SMS-a można przechwycić przez SIM swap, przeniesienie numeru lub złośliwe oprogramowanie, a użytkownik może go podać na fałszywej stronie. Aplikacje TOTP i klucze dostępu są odporniejsze.
Co to jest SIM swap?
SIM swap to przejęcie cudzego numeru telefonu: oszust przekonuje operatora do przeniesienia numeru na kartę SIM, którą kontroluje. Od tej chwili dostaje SMS-y ofiary, w tym kody logowania i odzyskiwania konta.
Czy własny telefon z Androidem nadaje się do wysyłania kodów OTP?
Do małych skali tak: aplikacja wewnętrzna, narzędzie dla zespołu, panel klienta z kilkudziesięcioma logowaniami dziennie. Nie nadaje się do serwisu konsumenckiego, bo telefon wysyła kilka–kilkanaście wiadomości na minutę, wymaga zasilania i zasięgu i jest jednym punktem awarii.
Jak długo powinien być ważny kod SMS?
Krótko: 5 minut to rozsądna wartość, a wytyczne NIST wymagają, by uwierzytelnienie poza kanałem uznawać za nieważne po 10 minutach. Ważne, bo gdy telefon jest offline, wiadomości czekają w kolejce do 72 godzin i kod mógłby dotrzeć po terminie.
Czym zastąpić SMS 2FA?
Najlepszą ochronę dają klucze dostępu (passkeys, WebAuthn). Dobrym, tanim wyborem jest aplikacja generująca kody TOTP. SMS zostaw jako opcję zapasową lub dla użytkowników, którzy nie mogą użyć niczego innego.


