VipBug VipBug
SMSvip

SMSvip

AndroidSMSSelf-hosted

Um gateway de SMS: seu site ganha a capacidade de enviar mensagens usando o SIM do seu próprio celular, sem contratar plano de envio. Ótimo para códigos de verificação 2FA, alertas e notificações. Tudo criptografado de ponta a ponta. Não é apropriado para disparo de SMS em massa.

Como usar

  1. Instale o app SMSvip num celular Android (de preferência com plano de mensagens ilimitadas) e deixe-o ligado na internet.
  2. Crie sua conta no app (nome, e-mail e senha).
  3. Na aba Sites, cadastre seu site. O app cria e mostra o id único do app, o id único e senhas de cada site.
  4. Nos seus sites, use o código abaixo com esses 3 valores.

Integração

Funciona em qualquer linguagem. O código monta a mensagem criptografada (AES-128-GCM), tenta enviar direto para o celular e se não conseguir pela limitação do ipv6 utiliza o nosso servidor para fazer relay.

Escolha abaixo qual linguagem você usa no seu backend:

<?php
const SMSVIP_PHP  = 'https://vipbug.com/smsvip';
const SMSVIP_APP  = 'UUID_DO_APP';
const SMSVIP_SITE = 'UUID_DO_SITE';
const SMSVIP_KEY  = 'SENHA_DO_SITE'; // 32 hex

function smsvip_send($to, $text) {
    $env = smsvip_seal($to, $text);
    // 1) tenta direto no celular
    $info = @file_get_contents(SMSVIP_PHP.'/resolve.php?app='.urlencode(SMSVIP_APP));
    $j = $info ? json_decode($info, true) : null;
    if ($j && !empty($j['online']) && !empty($j['url'])) {
        $r = smsvip_post($j['url'].'/relay', ['envelope'=>$env], 6);
        if ($r !== null) return json_decode($r, true);
    }
    // 2) fallback: relay
    $r = smsvip_post(SMSVIP_PHP.'/relay.php', ['uuid_app'=>SMSVIP_APP,'envelope'=>$env], 25);
    return $r !== null ? json_decode($r, true) : ['ok'=>false,'error'=>'sem conexao'];
}

function smsvip_seal($to, $text) {
    $key   = hex2bin(SMSVIP_KEY);
    $plain = json_encode(['to'=>$to,'text'=>$text,'ts'=>time(),'site'=>SMSVIP_SITE]);
    $nonce = random_bytes(12); $tag = '';
    $ct = openssl_encrypt($plain, 'aes-128-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag, '', 16);
    return base64_encode($nonce.$ct.$tag);
}

function smsvip_post($url, $data, $timeout) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS => json_encode($data),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_CONNECTTIMEOUT => 5,
    ]);
    $out = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch);
    return ($out !== false && $code) ? $out : null;
}

// uso:
$r = smsvip_send('+5585999999999', 'Seu codigo: 1234');
echo $r['ok'] ? 'enviado' : ('erro: '.$r['error']);
# pip install cryptography
import json, time, os, base64, urllib.request, urllib.parse
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

SMSVIP_PHP  = "https://vipbug.com/smsvip"
SMSVIP_APP  = "UUID_DO_APP"
SMSVIP_SITE = "UUID_DO_SITE"
SMSVIP_KEY  = "SENHA_DO_SITE"  # 32 hex

def _seal(to, text):
    key = bytes.fromhex(SMSVIP_KEY)
    plain = json.dumps({"to": to, "text": text, "ts": int(time.time()), "site": SMSVIP_SITE}).encode()
    nonce = os.urandom(12)
    ct = AESGCM(key).encrypt(nonce, plain, None)   # ciphertext + tag
    return base64.b64encode(nonce + ct).decode()

def _get(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as r:
        return json.loads(r.read())

def _post(url, data, timeout):
    req = urllib.request.Request(url, data=json.dumps(data).encode(),
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.loads(r.read())

def smsvip_send(to, text):
    env = _seal(to, text)
    try:
        info = _get(SMSVIP_PHP + "/resolve.php?app=" + urllib.parse.quote(SMSVIP_APP), 8)
        if info.get("online") and info.get("url"):
            try:
                return _post(info["url"] + "/relay", {"envelope": env}, 6)
            except Exception:
                pass
    except Exception:
        pass
    return _post(SMSVIP_PHP + "/relay.php", {"uuid_app": SMSVIP_APP, "envelope": env}, 25)

# uso:
print(smsvip_send("+5585999999999", "Seu codigo: 1234"))
// Node 18+ (fetch nativo)
const crypto = require('crypto');

const SMSVIP_PHP  = 'https://vipbug.com/smsvip';
const SMSVIP_APP  = 'UUID_DO_APP';
const SMSVIP_SITE = 'UUID_DO_SITE';
const SMSVIP_KEY  = 'SENHA_DO_SITE'; // 32 hex

function seal(to, text) {
  const key = Buffer.from(SMSVIP_KEY, 'hex');
  const plain = Buffer.from(JSON.stringify({ to, text, ts: Math.floor(Date.now() / 1000), site: SMSVIP_SITE }));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-128-gcm', key, iv);
  const ct = Buffer.concat([c.update(plain), c.final()]);
  return Buffer.concat([iv, ct, c.getAuthTag()]).toString('base64');
}

async function smsvipSend(to, text) {
  const env = seal(to, text);
  try {
    const info = await (await fetch(`${SMSVIP_PHP}/resolve.php?app=${encodeURIComponent(SMSVIP_APP)}`)).json();
    if (info.online && info.url) {
      try {
        const r = await fetch(`${info.url}/relay`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ envelope: env }), signal: AbortSignal.timeout(6000),
        });
        return await r.json();
      } catch (e) { /* cai no relay */ }
    }
  } catch (e) { /* cai no relay */ }
  const r = await fetch(`${SMSVIP_PHP}/relay.php`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ uuid_app: SMSVIP_APP, envelope: env }),
  });
  return await r.json();
}

// uso:
smsvipSend('+5585999999999', 'Seu codigo: 1234').then(console.log);
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Base64;

public class SmsVip {
    static final String PHP  = "https://vipbug.com/smsvip";
    static final String APP  = "UUID_DO_APP";
    static final String SITE = "UUID_DO_SITE";
    static final String KEY  = "SENHA_DO_SITE"; // 32 hex
    static final HttpClient HTTP = HttpClient.newHttpClient();

    public static String send(String to, String text) throws Exception {
        String env = seal(to, text);
        try {
            String info = get(PHP + "/resolve.php?app=" + APP, 8);
            if (info.contains("\"online\":true")) {
                String url = extract(info, "\"url\":\"", "\"");
                if (url != null) {
                    try { return post(url + "/relay", "{\"envelope\":\"" + env + "\"}", 6); }
                    catch (Exception ignore) {}
                }
            }
        } catch (Exception ignore) {}
        return post(PHP + "/relay.php", "{\"uuid_app\":\"" + APP + "\",\"envelope\":\"" + env + "\"}", 25);
    }

    static String seal(String to, String text) throws Exception {
        byte[] key = hex(KEY);
        long ts = System.currentTimeMillis() / 1000;
        String plain = "{\"to\":\"" + esc(to) + "\",\"text\":\"" + esc(text)
                     + "\",\"ts\":" + ts + ",\"site\":\"" + SITE + "\"}";
        byte[] iv = new byte[12];
        new SecureRandom().nextBytes(iv);
        Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
        c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
        byte[] ctTag = c.doFinal(plain.getBytes(StandardCharsets.UTF_8));
        byte[] env = new byte[12 + ctTag.length];
        System.arraycopy(iv, 0, env, 0, 12);
        System.arraycopy(ctTag, 0, env, 12, ctTag.length);
        return Base64.getEncoder().encodeToString(env);
    }

    static String get(String url, int sec) throws Exception {
        HttpRequest r = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(sec)).GET().build();
        return HTTP.send(r, HttpResponse.BodyHandlers.ofString()).body();
    }
    static String post(String url, String body, int sec) throws Exception {
        HttpRequest r = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(sec))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build();
        return HTTP.send(r, HttpResponse.BodyHandlers.ofString()).body();
    }
    static String esc(String s) { return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"); }
    static String extract(String s, String a, String b) {
        int i = s.indexOf(a); if (i < 0) return null; i += a.length();
        int j = s.indexOf(b, i); return j < 0 ? null : s.substring(i, j);
    }
    static byte[] hex(String h) {
        byte[] o = new byte[h.length() / 2];
        for (int i = 0; i < o.length; i++) o[i] = (byte) Integer.parseInt(h.substring(2*i, 2*i+2), 16);
        return o;
    }

    public static void main(String[] a) throws Exception {
        System.out.println(send("+5585999999999", "Seu codigo: 1234"));
    }
}

Procedimento

1. Copie o código que corresponde à linguagem do site que vai ser implementado.

2. Substitua no código as chaves: "UUID_DO_APP", "UUID_DO_SITE" e "SENHA_DO_SITE" pelos valores que você vê no app.;

3. Chame a função smsvip_send(T,M) ou smsvipsend(T,M) onde T é o número do celular que receberá o SMS e M é a mensagem que você quer enviar.

4. As últimas linhas de cada script é um exemplo de como usar.

Dúvidas ou sugestões

Caso tenha alguma dúvida ou queira apenas comentar alguma coisa sobre esse app, Sempre procuro responder!