Uma biblioteca de criptografia PHP contemporânea por volta de 2023.
Sinopse:
Esta biblioteca é um invólucro em torno da biblioteca de sódio PHP e da biblioteca PHP OpenSSL.
O código de sódio nesta biblioteca é baseado no exemplo fornecido na documentação da função php sodium_crypto_secretbox ().
O código OpenSSL nesta biblioteca é baseado no exemplo fornecido na documentação da função php openSSL_ENCRYPT ().
Esta biblioteca visa garantir que os dados que ele criptografam sejam tão seguros quanto as teclas secretas usadas para criptografá -las. Também são tomadas medidas na tentativa de garantir que os dados criptografados sejam à prova de adulteração.
Esta biblioteca não pode resolver o difícil problema do gerenciamento de chaves.

Esta biblioteca é um trabalho em andamento.
Estou compartilhando esse código com amigos e colegas, solicitando o máximo de críticas e feedback possível. Quando sinto que essa biblioteca é tão boa quanto eu posso fazer, atualizarei esta nota de status. Nesse meio tempo, as mudanças são quase certas e as fraquezas de criptografia são bem possíveis. Se você encontrar algo que você acha que eu deveria saber, por favor me avise!
Quero ficar claro com você que esta biblioteca é grande e complexa e não viu muito uso; É sem dúvida carregado com bugs sutis que ainda não foram descobertos. Eu acho que essa base de código tem o potencial de amadurecer em uma ferramenta sólida e confiável, mas precisamos passar pelo processo.
Por favor, leia esta seção.
Existem várias maneiras de errar com seu código criptográfico. Esta biblioteca foi escrita como uma tentativa de reduzir as punas de criptografia; Espero que não tenha introduzido nenhum!
A primeira coisa a saber sobre a criptografia é que seus dados são tão seguros quanto suas chaves. Há mais a saber sobre o gerenciamento -chave do que posso dizer aqui (e não sou um especialista de qualquer maneira), mas aqui estão algumas coisas em que pensar:
Algumas outras coisas a serem cientes:
get_error() após a criptografia para garantir que não seja indicador não.Outra coisa, o que me surpreendeu quando eu a aprendi, embora seja óbvio quando você souber, é que você não deve comprimir seus dados antes de criptografá -los. Isso nem sempre é um problema, mas em certas circunstâncias pode ser, então provavelmente é melhor apenas nunca fazê -lo.
O problema da compactação é que, se um invasor puder controlar alguns dos dados de entrada, poderão incluir um valor específico e, se a saída diminuir no tamanho, poderá saber que a outra entrada também incluiu no valor específico. Ai.
Essa base de código não é madura ou bem testada, antes de usá -lo, você deve ler todo o código para garantir que atenda aos seus padrões de qualidade. Se você o fizer, ficaria satisfeito em ouvir de você.
Se você consegue pensar em qualquer outra coisa que todos devem conhecer e ter cuidado, por favor me avise!
Não quero rtfm ..? E aqui estou eu, escrevendo todas essas coisas ... Sheesh. Pelo menos leia os avisos listados acima.
#!/bin/bash
set -euo pipefail;
mkdir -p kickass-demo/lib
cd kickass-demo
git clone https://github.com/jj5/kickass-crypto.git lib/kickass-crypto 2>/dev/null
php lib/kickass-crypto/bin/gen-demo-config.php > config.php
cat > demo.php <<'EOF'
<?php
require_once __DIR__ . '/lib/kickass-crypto/inc/sodium.php';
require_once __DIR__ . '/config.php';
$ciphertext = kickass_round_trip()->encrypt( 'secret text' );
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
echo "the secret data is: $plaintext.n";
EOF
php demo.php
Para um pouco mais de elaboração, talvez confira o código de amostra.
Ou se você deseja a linha de fundo de como essa biblioteca funciona, leia o código na estrutura da biblioteca ou no outro código.
Nossa, começou o suficiente, mas ficou meio complicado no final.
Eu queria viajar de volta alguns dados relativamente sensíveis (números de versão em linha para controle de simultaneidade otimista) entre meu servidor e seus clientes de maneira relativamente segura, segredo e preferência à prova de violação.
Ouvi dizer que a biblioteca OpenSSL estava disponível no PHP, então procurei informações sobre como usar isso. Encontrei o código de exemplo na documentação do PHP para a função OpenSSL_ENCRYPT ().
Inicialmente, não estava claro para mim como usar esse código. Particularmente, foi difícil descobrir o que fazer com as três partes: a tag de autenticação, o vetor de inicialização e o texto da cifra. Eventualmente, descobri que poderia concatená -los. Mas se eu fizesse isso, precisaria padronizar seu comprimento e colocação para que eu pudesse recuperá -los mais tarde ...
... e então eu imaginei que seria melhor mascarar meu tamanho de dados real, preenchendo -o em comprimentos fixos em determinados limites, então eu fiz isso ...
... e então eu queria apoiar dados ricos que exigiam alguma forma de serialização. Inicialmente, eu estava usando a função php serialize (), mas isso foi alterado posteriormente para json_encode ().
O código de exemplo não indicou nada sobre como girar as teclas de maneira suportada. Então, criei os dois casos de uso suportados por esta biblioteca com diferentes abordagens para o gerenciamento de chaves para cenários de ida e volta e de repouso. Esta biblioteca permite que você gire em novas teclas, mantendo o suporte para teclas mais antigas, como você provavelmente costuma fazer.
Em seguida, em camadas em uma abordagem cuidadosa para manuseio de exceções e relatórios de erros, alguns testes e validação de unidade, mitigação de ataques de tempo, localizadores de serviços, demonstração de uso, limites de tamanho de dados, inicialização da senha, scripts de geração de chaves, telemetria e coisas assim.
Basicamente, toda essa biblioteca era tudo o que eu sentia que tinha que fazer para que eu pudesse realmente usar a implementação da biblioteca PHP OpenSSL embutida.
E então ... as pessoas começaram a me contar sobre a biblioteca de sódio, e sugerindo que eu uso isso. Como já eu já tinha feito um monte de trabalho para gerenciamento de chaves e serialização de entrada e formatação e codificação de mensagens, e assim, achei que poderia reutilizar tudo isso e fornecer um invólucro em torno do sódio também. Então foi isso que eu fiz.
Agora, se você usar esta biblioteca, poderá decidir se deseja usar a implementação de sódio ou a implementação do OpenSSL. Como as duas implementações podem coexistir felizes, você também pode escrever código para passar de um para o outro, se você desejar. As implementações nunca compartilham os principais formatos de configuração ou dados, são totalmente separados. (Dito isto, não é exatamente trivial trocar de algoritmos de criptografia e você provavelmente terá que ficar offline para migrar todos os seus dados e, se não puder fazer isso, terá um tempo ruim para que não planeje alternar os algoritmos, se não tiver certeza, comece com o sódio e fique com ele.)
Não considero esta biblioteca rolando minha própria criptografia , mas penso nisso como descobrir como realmente usar o sódio e o OpenSSL . Se eu cometi algum erro, óbvio ou não, eu realmente gostaria de ouvir sobre isso.
Supondo que me lembro de atualizá -lo de tempos em tempos, há um sistema de demonstração aqui:
A instalação de demonstração apenas mostra como os dados criptografados entre o cliente e o servidor usando HTML e HTTP.
O código de demonstração está disponível nesta biblioteca no SRC/ Demo/ Diretório, se você quiser hospedá -lo.
Supondo que me lembro de atualizá -los de tempos em tempos, os documentos PHP estão aqui:
Como mencionado acima, você pode conferir o código do Git com um comando como este:
git clone https://github.com/jj5/kickass-crypto.git
Este código não está lançado, não há uma versão estável.
Se você deseja incluir a biblioteca do cliente para uso em seu aplicativo, inclua o arquivo inc/sodium.php ou inc/openSSL.php que cuidará de carregar todo o resto; Use algo assim:
require_once __DIR__ . '/lib/kickass-crypto/inc/sodium.php';
Depois de carregar esta biblioteca, você geralmente acessa através dos localizadores de serviço kickass_round_trip() ou kickass_at_rest() que estão documentados abaixo, algo assim:
$ciphertext = kickass_round_trip()->encrypt( 'secret text' );
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
echo "the secret data is: $plaintext.n";
Demorou muito trabalho para tornar as coisas tão simples!
Se você deseja hospedar o código de demonstração, precisará hospedar os arquivos no SRC/ Demo/ e incluir um arquivo config.php válido no diretório base do projeto (esse é o diretório que inclui esse arquivo readme.md). Para fins de demonstração, um arquivo config.php válido precisa definir apenas uma sequência constante para CONFIG_SODIUM_SECRET_CURR , mas precisa ser uma string longa e aleatória, você pode gerar uma string apropriada com:
php bin/gen-key.php
Ou você pode apenas gerar um arquivo config.php inteiro com:
php bin/gen-demo-config.php > config.php
Aqui estão algumas notas sobre os arquivos de software e linhas de código.
Total Number of Files = 128
Total Number of Source Code Files = 128
| Diretório | Arquivos | Por idioma |
|---|---|---|
| teste | 63 | php = 59, sh = 4 |
| código | 35 | php = 35 |
| BIN | 22 | php = 13, sh = 9 |
| Inc | 7 | php = 7 |
| demonstração | 1 | php = 1 |
| Linguagem | Arquivos | Percentagem |
|---|---|---|
| php | 115 | (89,84%) |
| sh | 13 | (10,16%) |
Total Physical Source Lines of Code (SLOC) = 9,210
Development Effort Estimate, Person-Years (Person-Months) = 2.06 (24.70)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months) = 0.70 (8.46)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule) = 2.92
Total Estimated Cost to Develop = $ 278,044
(average salary = $56,286/year, overhead = 2.40).
| Diretório | Sloc | Por idioma |
|---|---|---|
| código | 5.136 | PHP = 5136 |
| teste | 3.363 | php = 3193, sh = 170 |
| BIN | 603 | php = 423, sh = 180 |
| demonstração | 71 | php = 71 |
| Inc | 37 | php = 37 |
| Linguagem | Sloc | Percentagem |
|---|---|---|
| php | 8.860 | (96,20%) |
| sh | 350 | (3,80%) |
Este código deve funcionar no PHP 7.4 ou superior. Se você tentar executar esse código em uma versão mais antiga do PHP, ele tentará registrar uma mensagem de erro e sair do seu processo.
Este código verá para garantir que esteja em execução em uma plataforma de 64 bits. Se não for, isso vai reclamar e sair.
Se você carregar o módulo de sódio, a biblioteca garantirá que a biblioteca de sódio esteja realmente disponível. Caso contrário, o processo reclamará e sairá.
Se você carregar o módulo OpenSSL, a biblioteca garantirá que a biblioteca OpenSSL esteja realmente disponível. Caso contrário, o processo reclamará e sairá.
Acredito que esse código deve ser executado em qualquer sistema operacional, mas eu o testei apenas no Linux. Se você teve sucesso no macOS ou no Windows, ficaria feliz em ouvir sobre isso.
Os scripts de shell são escritos para Bash. Se você não tiver Bash, pode ser necessário portar.
Este código suporta dois casos de uso específicos:
As chaves são gerenciadas separadamente e de maneira diferente para cada caso de uso.
Os detalhes de como cada caso de uso é suportado estão documentados abaixo.
O uso desta biblioteca para a criptografia de restrição é geralmente um risco maior e um compromisso maior do que usá-lo simplesmente para criptografia de ida e volta. Se você perder suas chaves de criptografia de ida e volta ou for forçado a girá-las com urgência, isso provavelmente será menos um problema do que se algo semelhante acontecesse com suas chaves de restrição.
O principal caso de uso para o qual essa biblioteca foi desenvolvida foi apoiar a trégua de alguns kilobytes de dados que contêm números levemente sensíveis, mas não de missão crítica, para controle de concorrência otimista. Em comparação com a alternativa (não criptografar ou impermeabilizar os dados otimistas de controle de simultaneidade), o uso dessa biblioteca é uma melhoria. Se é realmente adequado em outras aplicações é uma questão em aberto, não tenho certeza. Certamente você não deve usar esta biblioteca se não fornecer o nível de segurança necessário.
A maneira preferida e suportada de nomear segredos nos arquivos de configuração é como constantes usando a função PHP define (). O problema do uso de campos de classe/instância ou variáveis globais é que os valores podem vazar facilmente no código de depuração e log, isso é menos provável (embora ainda seja possível) para constantes. Da mesma forma, se você precisar armazenar em cache dados globais/estáticos (como leitura do arquivo de configuração), a melhor maneira de fazer isso é com uma variável estática local em uma função, se possível, como o uso de campos de instância, campos de classe ou globais podem levar mais facilmente a vazamentos secretos.
Para dar um exemplo, vamos criar um arquivo de teste chamado double-define.php como este:
<?php
define( 'TEST', 123 );
define( 'TEST', 456 );
Então, quando executamos o código, algo assim acontece:
$ php double-define.php
PHP Warning: Constant TEST already defined in ./double-define.php on line 4
PHP Stack trace:
PHP 1. {main}() ./double-define.php:0
PHP 2. define($constant_name = 'TEST', $value = 456) ./double-define.php:4
Se esse valor constante continha sua chave secreta, você acabou de ter um dia muito ruim.
A maneira mais segura de definir uma constante no PHP é verificar se ele ainda não está definido primeiro, porque tentar definir uma constante já definida resultará em erro. Se você encontrar uma constante já definida, poderá abortar com uma mensagem de erro (se você não fornecer muitos detalhes porque a Web pública poderá vê -la) ou apenas mantenha o valor existente e não tente redefini -lo. O gerador de arquivos de configuração Bin/Gen-Demo-Config.php adota a primeira abordagem e chama a função PHP die() se uma duplicata for detectada. Você pode ver o que acontece incluindo o arquivo Gerated config.php duas vezes, como:
require __DIR__ . '/config.php';
require __DIR__ . '/config.php';
Você pode encontrar um exemplo do que acontece se você incluir o config.php no config-die.php.
Consequentemente, como na maioria dos arquivos de origem do PHP, é melhor usar require_once ao incluir o arquivo config.php :
require_once __DIR__ . '/config.php';
Quando nomeio coisas que são secretas, garanto que o nome contém a string "Pass" (como em "Senha", "Passwd" e "Passprae", ou até, em um trecho, "passaporte") ou "secreto". Nas minhas instalações de registro de propósito geral (que não estão incluídas nesta biblioteca), esfrego e redigo qualquer coisa com um nome que corresponda (insensível ao caso) antes de registrar dados de diagnóstico. Encorajo você a adotar essa prática.
Nesta biblioteca, se uma variável ou constante puder conter dados sensíveis, será nomeado com "passe" ou "secreto" como uma substring no nome.
Não escreva dados confidenciais em logs.
Coloque 'passe' ou 'secreto' em nome de variáveis, campos ou constantes sensíveis.
Aqui eu explico o que esses termos de som semelhante realmente significam no contexto desta biblioteca.
Se você usar os módulos padrão, o formato de dados será "KA0" para o módulo OpenSSL ou "KAS0" para o módulo de sódio.
If you inherit the base framework and define your own crypto module the default data format is "XKA0" for a module based on the OpenSSL implementation or "XKAS0" for a module based on the Sodium implementation otherwise your implementation of do_get_const_data_format() determines what the data format will be known as, you can make it anything so long as it doesn't start with the string "KA" which is reserved for official implementações.
Você precisa usar o módulo certo para o formato de dados para descriptografar com sucesso um texto cifrado.
A codificação de dados é JSON, serialização PHP ou texto. Supondo que você tenha o módulo certo para o formato de dados (acima) e, com uma advertência discutida abaixo, você pode descriptografar qualquer coisa, independentemente dos dados que ele utilizou. A criptografia será feita usando a codificação de dados configurada, consulte Config_Encryption_Data_Encoding, pode ser um dos:
Observe que você não poderá usar a codificação de PHPs, a menos que também definir config_encryption_phps_enable, isso ocorre porque a deserialização do PHP pode ser insegura, por isso é desativado por padrão. Honestamente, isso é um pouco onda de mão. Acabei de ouvir rumores de que o php unserialize() pode levar à injeção de código, mas não tenho certeza se isso é verdade ou o que exatamente isso significa. Implementei a serialização e a desserialização do PHP e dei um pouco de teste, mas não sei se é realmente inseguro ou não. Tenho certeza de que a codificação de dados JSON e Text deve estar segura.
Além de herdar do KickassCrypto e substituir a funcionalidade específica, muita configuração está disponível através das constantes de configuração. Pesquise CONFIG_SODIUM para encontrar o que está disponível para Sodium e CONFIG_OPENSSL para encontrar o que está disponível para o OpenSSL.
Esteja aconselhado que, no momento, este código esteja configurado diretamente no arquivo config.php .
No futuro, o arquivo config.php incluirá arquivos de configuração gerenciados separadamente, sendo:
Haverá scripts de gerenciamento para as chaves de girar e provisionar automaticamente nesses arquivos.
Usuários experientes do Linux sabem que você não edita /etc/sudoers diretamente, edita -o com visudo para poder verificar se você não introduziu acidentalmente um erro de sintaxe e manifestou seu sistema.
Pretendo fornecer scripts semelhantes para editar e gerenciar config.php e outros arquivos de configuração. Então, stand-by para essas atualizações. Enquanto isso ... apenas tenha muito cuidado .
Uma coisa que você deve ter muito cuidado para não fazer é gerenciar suas chaves em algo que não seja um arquivo PHP com uma extensão de arquivo ".php". Se você colocar suas chaves em um arquivo ".ini" ou algo assim , elas poderão muito bem ser servidas como texto simples pelo seu servidor da web . Então não faça isso. Também tenha cuidado para não introduzir erros de sintaxe no arquivo de configuração ou em outros arquivos de origem em execução na produção, porque os detalhes podem vazar com as mensagens de erro em potencial resultantes.
Conforme mencionado na seção anterior, uma quantidade razoável de configurabilidade é fornecida pelo suporte para constantes de configuração nomeadas.
Além das constantes de configuração, há muita coisa que você pode fazer se herdar da classe base KickassCrypto e substituir seus métodos.
Como alternativa às constantes de configuração (que só podem ser definidas uma vez por processo e, posteriormente, não podem ser alteradas), existem métodos de instância como get_config_...() para opções de configuração e get_const_...() para avaliação constante. As constantes mais importantes e as opções de configuração são lidas indiretamente por meio desses acessores, para que você possa substituí -las de maneira confiável.
A maioria das chamadas para funções internas de PHP é feita por embalagens finas por meio de funções protegidas no KickassCrypto . Estes são definidos na característica KICKASS_WRAPPER_PHP . Esse indireção permite que certas invocações de função PHP sejam interceptadas e potencialmente modificadas. Isso foi feito principalmente para apoiar a injeção de falhas durante o teste de unidade, mas você pode usar para outros fins para alterar os detalhes da implementação.
Coisas consideradas sensíveis no KickassCrypto são definidas como privadas ou finais . Se não for privado e não é final, é um jogo justo para substituir (a menos que eu tenha cometido um erro). Particularmente, os métodos de instância que começam com do_ foram feitos especificamente para serem substituídos ou interceptados pelos implementadores.
Esta biblioteca fornece duas funções do localizador de serviços que gerenciam uma instância da biblioteca criptográfica cada uma, essas são:
kickass_round_trip()kickass_at_rest()Você pode substituir a instância do serviço fornecido pela função do localizador de serviço chamando a função e passando a nova instância como o único parâmetro, como este:
class MyKickassCrypto extends KickassCryptoKickassCrypto {
protected function do_is_valid_config( &$problem = null ) { return TODO; }
protected function do_get_passphrase_list() { return TODO; }
// ... other function overrides ...
}
kickass_round_trip( new MyKickassCrypto );
Idealmente, esta biblioteca atenderá aos seus requisitos prontos para a caixa (ou com determinada configuração) e você não precisará substituir as instâncias fornecidas pelos localizadores de serviço por padrão.
Um localizador de serviço criará uma nova instância padrão para você na primeira chamada para o localizador de serviço, se ainda não tiver uma instância. Se a implementação padrão é o módulo de sódio ou o módulo OpenSSL depende do pedido que você incluiu os arquivos inc/sodium.php e inc/openssl.php ; Se você incluiu toda a biblioteca com inc/library.php o módulo de sódio terá precedência.
Independentemente de você carregar os localizadores de serviço para o módulo de sódio ou o módulo OpenSSL, poderá substituir a instância padrão chamando o localizador de serviço com uma nova instância como argumento.
O processo de criptografia é aproximadamente:
Observe que a biblioteca de sódio usa um nonce em vez de um vetor de inicialização (para efeito semelhante) e o sódio lida com sua própria tag de autenticação.
Quando esta biblioteca codifica seu texto cifrado, inclui um prefixo de formato de dados de "KAS0/" para a implementação de sódio e "Ka0/" para a implementação do OpenSSL.
O zero ("0") no prefixo formato de dados é para a versão zero , o que se destina a sugerir que a interface é instável e pode mudar .
As versões futuras desta biblioteca podem implementar um novo prefixo formato de dados para um formato de dados estável.
Quando esta biblioteca decodifica seu texto cifrado, verifica o prefixo formato de dados. Atualmente, apenas "kas0/" ou "ka0/" é suportado.
A versão zero de formato de dados, mencionado acima, atualmente implica o seguinte:
Após a codificação de dados (JSON por padrão, discutido na seção a seguir), o preenchimento é realizado e o comprimento dos dados é prefixado. Antes da criptografia, a mensagem é formatada, como esta:
$message = $encoded_data_length . '|json|' . $encoded_data . $this->get_padding( $pad_length );
O comprimento dos dados JSON é formatado como um valor hexadecimal de 8 caracteres. O tamanho de 8 caracteres é constante e não varia dependendo da magnitude do comprimento dos dados JSON.
O motivo do preenchimento é obscurecer o tamanho real dos dados. O preenchimento é feito em até 4 limites de kib (2 12 bytes), que chamamos de pedaços. O tamanho do pedaço é configurável e o padrão pode mudar no futuro.
Então, se estamos criptografando com sódio, a mensagem é criptografada com sodium_crypto_secretbox() e então Nonce e o CipherText são concatenados juntos, assim:
$nonce . $ciphertext
Caso contrário, se estivermos criptografando com o OpenSSL, a mensagem é criptografada com AES-256-GCM e o vetor de inicialização, o CipherText e a Authentication Tag são concatenados juntos, assim:
$iv . $ciphertext . $tag
Então tudo é base64 codificado com a função php base64_encode () e o prefixo formato de dados é adicionado.
Para o sódio, isso é feito assim:
"KAS0/" . base64_encode( $nonce . $ciphertext )
E para o OpenSSL que é feito assim:
"KA0/" . base64_encode( $iv . $ciphertext . $tag )
O processo de descriptografia espera encontrar o Nonce de 24 bytes e o texto cifrado para o formato de dados "KAS0" e o vetor de inicialização de 12 bytes, o texto cifrado e a tag de autenticação de 16 bytes para o formato de dados KA0.
Depois de descriptografar o texto cifrado, a biblioteca espera encontrar o tamanho dos dados JSON como uma sequência ASCII que representa um valor codificado de 8 caracteres, seguido por um único caractere de tubo, seguido por um indicador de codificação de quatro caracteres (JSON 'ou' PHPS '), seguido por um único caractere, seguido pelo JSON (ou PHP Série Série) e Dados Série), e Dados Série) e PHP) e PHP) e PHP) e PHP) e PHP) e, depois, o PHP. A biblioteca pode extrair os dados JSON/serializados de seu preenchimento e cuidar do restante da decodificação.
Antes da criptografia, os dados de entrada são codificados como JSON usando a função php json_encode (). Inicialmente, essa biblioteca usou a função php serialize (), mas aparentemente isso pode levar a alguns cenários de execução de código (não tenho certeza dos detalhes), por isso foi decidido que a codificação do JSON era mais segura. Assim, agora, usamos a codificação do JSON.
O uso do JSON como formato de codificação de dados tem algumas implicações menores em relação aos valores que podemos suportar. Particularmente, não podemos codificar instâncias de objetos que podem ser decodificadas posteriormente para instâncias de objetos (se os objetos implementarem a interface JSOSerializable, poderão ser serializados como dados, mas esses serão decodificados apenas para matrizes de PHP, não os objetos PHP dos quais vieram); Alguns valores de ponto flutuante estranhos não podem ser representados (ou seja, NAN, POS INF, NEG INFO e NEG ZERO); E as cordas binárias não podem ser representadas em JSON.
Por padrão, essas opções são usadas para a codificação JSON:
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
Mas essas opções não afetarão a capacidade de uma implementação de decodificar o JSON. As implementações podem ajustar a codificação e decodificação JSON, se necessário, substituindo os métodos Data_Encode () e Data_Decode (). Como alternativa, você pode nomear as opções de codificação e decodificação do JSON em seu arquivo config.php com o CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS and CONFIG_ENCRYPTION_JSON_DECODE_OPTIONS constantes, por exemplo:
define( 'CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
define( 'CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS', JSON_THROW_ON_ERROR );
Esta biblioteca deve funcionar, independentemente de JSON_THROW_ON_ERROR ser especificado ou não.
Se você especificar JSON_PARTIAL_OUTPUT_ON_ERROR nas suas opções de codificação JSON, seus dados podem se tornar silenciosamente inválidos, faça isso por sua conta e risco. Talvez contra-intuitivamente, descobri que permitir JSON_PARTIAL_OUTPUT_ON_ERROR é a pior estratégia, porque pelo menos nesse caso você obtém alguma coisa . Se você não habilitar JSON_PARTIAL_OUTPUT_ON_ERROR , se alguma parte da sua entrada não puder ser codificada (como quando você possui seqüências binárias que não estão em uma codificação válida como UTF-8), todos os dados serão removidos. Com JSON_PARTIAL_OUTPUT_ON_ERROR apenas a parte não representável é omitida. No momento, JSON_PARTIAL_OUTPUT_ON_ERROR não é especificado automaticamente, mas isso é algo que eu posso revisitar no futuro.
Se você usar alguma dessas opções de codificação/decodificação JSON, poderá muito bem acabar se divertindo:
JSON_NUMERIC_CHECKJSON_INVALID_UTF8_IGNOREJSON_INVALID_UTF8_SUBSTITUTE Quando esta biblioteca criptografa seus dados, ele é de sua saída até um tamanho de pedaço configurável.
A constante de configuração para o tamanho do chunk é CONFIG_ENCRYPTION_CHUNK_SIZE .
O tamanho do pedaço padrão é 4.096 (2 12 ).
Se você quisesse aumentar o tamanho do pedaço para 8.192, poderá fazer isso em seu arquivo config.php como este:
define( 'CONFIG_ENCRYPTION_CHUNK_SIZE', 8912 );
Você pode alterar o tamanho do pedaço definido e ele começará a se aplicar a novos dados, e os dados antigos criptografados com um tamanho de pedaço diferente ainda poderão ser descriptografados.
Enquanto os limites de tamanho de dados forem observados (eles serão discutidos a seguir), essa biblioteca pode criptografar qualquer coisa que possa ser codificada como JSON pelo PHP.
Isso inclui uma variedade de coisas, como:
Coisas que não podem ser apoiadas com JSON:
Observe que o valor booleano false não pode ser criptografado. Não é porque não conseguimos criptografá -lo, é porque o devolvemos quando a descriptografia falhar. Por isso, nos recusamos a criptografar false para que ele não possa ser confundido com um erro após a descriptografia.
Se você precisar criptografar o valor booleano falso, considere colocá -lo em uma matriz, assim:
$input = [ 'value' => false ];
Ou codificando como JSON, assim:
$input = json_encode( false );
Se você fizer uma dessas coisas, poderá criptografar seu valor.
Vale ressaltar que no PHP "Strings" são essencialmente matrizes de bytes, o que significa que eles podem conter dados essencialmente "binários". Tais dados binários não podem ser representados como JSON. Se você precisar lidar com dados binários, a melhor maneira provavelmente é codificá -los como base64 com base64_encode () ou hexadecimal com bin2hex () e depois criptografar isso.
No futuro, a capacidade de trabalhar com dados que nem sempre codificam o JSON pode ser adicionada a esta biblioteca. Deixe -me saber se esse é um recurso que você deseja ter.
Nota: Usar a serialização do PHP em vez da codificação JSON agora é uma opção; Esta documentação precisa ser atualizada para explicar como funciona e como usá -lo. A vantagem da serialização do PHP é que ele suporta mais tipos e formatos de dados do que o JSON.
Depois que os dados são codificados como JSON, ele é limitado a um comprimento máximo configurável.
A constante de configuração para o comprimento máximo de codificação JSON é CONFIG_ENCRYPTION_DATA_LENGTH_MAX .
O limite padrão de codificação de dados é de 67.108.864 (2^ 26 ) bytes, que são aproximadamente 67 MB ou 64 MIB.
É possível configurar esse limite de codificação de dados, se você precisar torná -lo maior ou menor. Esteja ciente de que, se você tornar o limite muito grande, acabará com problemas de memória e seu processo poderá ser encerrado.
Se você quiser diminuir o limite de codificação de dados, poderá fazer isso em seu arquivo config.php como este:
define( 'CONFIG_ENCRYPTION_DATA_LENGTH_MAX', pow( 2, 25 ) );
Esta biblioteca não comprime dados de entrada, porque a compactação pode introduzir fraquezas criptográficas, como no ataque do crime SSL/TLS.
O problema é que, se o invasor puder modificar parte do texto simples, poderá descobrir se os dados que eles inseram existirem em outras partes do texto simples, porque se eles investirem um valor e o resultado for menor, porque existe na parte do texto sem formatação que não sabiam, mas agora!
É muito importante que você não compacte dados que um invasor possa fornecer com outros dados secretos. É melhor não apenas comprimir.
Se um erro for encontrado durante a criptografia ou descriptografia, será introduzido um atraso entre 1 milissegundo (1 ms) e 10 segundos (10 s). Esta é uma mitigação contra possíveis ataques de tempo. Veja S2N e Lucky 13 para discussão.
Note that avoiding timing attacks is hard. A malicious guest on your VPS host (or a malicious person listening to your server's fans! ?) could figure out that your process is sleeping rather than doing actual work.
This library includes a method called delay() , and this method is called automatically on the first instance of an error. The delay() method does what is says on the tin: it injects a random delay into the process. The delay() method is public and you can call it yourself if you feel the need. Each time delay() is called it will sleep for a random amount of time between 1 millisecond and 10 seconds.
The programmer using this library has the opportunity to override the do_delay() method and provide their own delay logic.
If that do_delay() override throws an exception it will be handled and an emergency delay will be injected.
If you do override do_delay() but don't actually delay for at least the minimum duration (which is 1 ms) then the library will inject the emergency delay.
The main reason for allowing the implementer to customize the delay logic is so that unit tests can delay for a minimum amount of time. Ordinarily there shouldn't be any reason to meddle with the delay logic and it might be less safe to do so.
When an instance of one of of the following is created the configuration settings are validated.
KickassSodiumRoundTripKickassSodiumAtRestKickassOpenSSLRoundTripKickassOpenSSLAtRestIf the configuration settings are not valid the constructor will throw an exception. If the constructor succeeds then encryption and decryption later on should also (usually) succeed. If there are any configuration problems that will mean encryption or decryption won't be able to succeed (such as secret keys not having been provided) the constructor should throw.
This library defines its own exception class called KickassException . This works like a normal Exception except that it adds a method getData() which can return any data associated with the exception. A KickassException doesn't always have associated data.
Of course not all problems will be able to be diagnosed in advance. If the library can't complete an encryption or decryption operation after a successful construction it will signal the error by returning the boolean value false. Returning false on error is a PHP idiom, and we use this idiom rather than raising an exception to limit the possibility of an exception being thrown while an encryption secret or passphrase is on the call stack.
The problem with having sensitive data on the call stack when an exception is raised is that the data can be copied into stack traces, which can get saved, serialized, displayed to users, logged, etc. We don't want that so we try very hard not to raise exceptions while sensitive data might be on the stack.
If false is returned on error, one or more error messages will be added to an internal list of errors. The caller can get the latest error by calling the method get_error . If you want the full list of errors, call get_error_list .
If there were any errors registered by the OpenSSL library functions (which the OpenSSL module calls to do the heavy lifting), then the last such error is available if you call the get_openssl_error() . You can clear the current error list (and OpenSSL error message) by calling the method clear_error() .
For the PHP Sodium implementation the function we use is sodium_crypto_secretbox(). That's XSalsa20 stream cipher encryption with Poly1305 MAC authentication and integrity checking.
For the PHP OpenSSL implementation the cipher suite we use is AES-256-GCM. That's Advanced Encryption Standard encryption with Galois/Counter Mode authentication and integrity checking.
Secret keys are the secret values you keep in your config.php file which will be processed and turned into passphrases for use by the Sodium and OpenSSL library functions. This library automatically handles converting secret keys into passphrases so your only responsibility is to nominate the secret keys.
The secret keys used vary based on the use case and the module. There are two default use cases, known as round-trip and at-rest.
The "256" in AES-256-GCM means that this cipher suite expects 256-bit (32 byte) passphrases. The Sodium library sodium_crypto_secretbox() function also expects a 256-bit (32 byte) passphrase.
We use a hash algorithm to convert our secret keys into 256-bit binary strings which can be used as the passphrases the cipher algorithms expect.
The minimum secret key length required is 88 bytes. When these keys are generated by this library they are generated with 66 bytes of random data which is then base64 encoded.
The secret key hashing algorithm we use is SHA512/256. That's 256-bits worth of data taken from the SHA512 hash of the secret key. When this hash code is applied with raw binary output from an 88 byte base64 encoded input you should be getting about 32 bytes of randomness for your keys.
The Sodium library expects to be provided with a nonce, in lieu of an initialization vector.
To understand what problem the nonce mitigates, think about what would happen if you were encrypting people's birthday. If you had two users with the same birthday and you encrypted those birthdays with the same key, then both users would have the same ciphertext for their birthdays. When this happens you can see who has the same birthday, even when you might not know exactly when it is. The initialization vector avoids this potential problem.
Our AES-256-GCM cipher suite supports the use of a 12 byte initialization vector, which we provide. The initialization vector ensures that even if you encrypt the same values with the same passphrase the resultant ciphertext still varies.
This mitigates the same problem as the Sodium nonce.
Our AES-256-GCM cipher suite supports the validation of a 16 byte authentication tag.
The "GCM" in AES-256-GCM stands for Galois/Counter Mode. The GCM is a Message Authentication Code (MAC) similar to a Hash-based Message Authentication Code (HMAC) which you may have heard of before. The goal of the GCM authentication tag is to make your encrypted data tamperproof.
The Sodium library also uses an authentication tag but it takes care of that by itself, it's not something we have to manage. When you parse_binary() in the Sodium module the tag is set to false.
This library requires secure random data inputs for various purposes:
There are two main options for generating suitable random data in PHP, those are:
Both are reasonable choices but this library uses random_bytes().
If the random_bytes() function is unable to generate secure random data it will throw an exception. See the documentation for details.
We also use the PHP random_int() function to generate a random delay for use in timing attack mitigation.
The round-trip use case is for when you want to send data to the client in hidden HTML form <input> elements and have it POSTed back later.
This use case is supported with two types of secret key.
The first key is called the current key and it is required.
The second key is called the previous key and it is optional.
Data is always encrypted with the current key.
Data is decrypted with the current key, and if that fails it is decrypted with the previous key. If decryption with the previous key also fails then the data cannot be decrypted, in that case the boolean value false will be returned to signal the error.
When you rotate your round-trip secret keys you copy the current key into the previous key, replacing the old previous key, and then you generate a new current key.
The config setting for the current key for the Sodium module is: CONFIG_SODIUM_SECRET_CURR .
The config setting for the current key for the OpenSSL module is: CONFIG_OPENSSL_SECRET_CURR .
The config setting for the previous key for the Sodium module is: CONFIG_SODIUM_SECRET_PREV .
The config setting for the previous key for the OpenSSL module is: CONFIG_OPENSSL_SECRET_PREV .
To encrypt round-trip data:
$ciphertext = kickass_round_trip()->encrypt( 'secret data' );
To decrypt round-trip data:
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
The at-rest use case if for when you want to encrypt data for storage in a database or elsewhere.
This use case is supported with an arbitrarily long list of secret keys.
The list must include at least one value. The first value in the list is used for encryption. For decryption each secret key in the list is tried until one is found that works. If none work the data cannot be decrypted and the boolean value false is returned to signal the error.
When you rotate your at-rest secret keys you add a new master key as the first item in the list. You need to keep at least one extra key, and you can keep as many in addition to that as suits your purposes.
After you rotate your at-rest secret keys you should consider re-encrypting all your existing at-rest data so that it is using the latest key. After you have re-encrypted your at-rest data, you can remove the older key.
The config setting for the key list for the Sodium module is: CONFIG_SODIUM_SECRET_LIST .
The config setting for the key list for the OpenSSL module is: CONFIG_OPENSSL_SECRET_LIST .
Please be aware: if you restore an old backup of your database, you will also need to restore your old keys.
Be very careful that you don't lose your at-rest secret keys. If you lose these keys you won't be able to decrypt your at-rest data.
To encrypt at-rest data:
$ciphertext = kickass_at_rest()->encrypt( 'secret data' );
To decrypt at-test data:
$plaintext = kickass_at_rest()->decrypt( $ciphertext );
It has been noted that key management is the hardest part of cybersecurity. This library can't help you with that.
Your encrypted data is only as secure as the secret keys.
If someone gets a copy of your secret keys, they will be able to decrypt your data.
If someone gets a copy of your encrypted data now, they can keep it and decrypt it if they get a copy of your secret keys in the future. So your keys don't have to be only secret now, but they have to be secret for all time.
If you lose your secret keys, you won't be able to decrypt your data.
Your round-trip data is probably less essential than your at-rest data.
It's a very good idea to make sure you have backups of the secret keys for your essential round-trip or at-rest data. You can consider:
When doing key management it is important to make sure your config files are edited in a secure way. A syntax error in a config file could lead to a secret key being exposed to the public web. If this happened you would have to rotate all of your keys immediately and then destroy the old compromised keys, even then it might be too late .
It would be a good idea to stand ready to do a key rotation in an automated and tested fashion immediately in case of emergency.
When you rotate your round-trip and at-rest keys you need to make sure they are synchronized across all of your web servers.
I intend to implement some facilities to help with key deployment and config file editing but those facilities are not done yet.
This library supports encrypted data at-rest, and encrypted data round-trips. Another consideration is data in motion. Data in motion is also sometimes called data in transit.
Data is in motion when it moves between your web servers and your database server. Data is also in motion when it moves between your web servers and the clients that access them. You should use asymmetric encryption for your data in motion. Use SSL encryption support when you connect to your database, and use HTTPS for your web clients.
This library is a server-side component. We don't support encrypting data client-side in web browsers.
This library collects some basic telemetry:
Call KickassCrypto::GetTelemetry() to get the telemetry and KickassCrypto::ReportTelemetry() to report it.
The unit tests are in the src/test/ directory, numbered sequentially.
There's some test runners in bin/dev/, as you can see. Read the scripts for the gory details but in brief:
There are also some silly tests, but we won't talk about those. They are not ordinarily run. And they're silly.
If you want to add a normal/fast test create the unit test directory as src/test/test-XXX , then add either fast.php or fast.sh . If you create both then fast.sh will have precedence and fast.php will be ignored.
If you want to add a slow test create the unit test directory as src/test/test-XXX , then add either slow.php or slow.sh . If you create both then slow.sh will have precedence and slow.php will be ignored.
You usually only need to supply a shell script if your unit tests require multiple processes to work. That can happen when you need to test different constant definitions. As you can't redefine constants in PHP you have to restart your process if you want to run with different values.
See existing unit tests for examples of how to use the simple unit test host.
I have heard of and used PHPUnit (although I haven't used it for a long while). I don't use it in this project because I don't feel I need it or that it adds much value. Tests are a shell script, if that's missing they're a PHP script. If I need to make assertions I call assert(). Fácil.
Here are some notes about the various idioms and approaches taken in this library.
In the code you will see things like this:
protected final function is_valid_settings( int $setting_a, string $setting_b ) : bool {
if ( strlen( $setting_b ) > 20 ) { return false; }
return $this->do_is_valid_settings( $setting_a, $setting_b );
}
protected function do_is_valid_settings( $setting_a, $setting_b ) {
if ( $setting_a < 100 ) { return false; }
if ( strlen( $setting_b ) > 10 ) { return false; }
return 1;
}
There are several things to note about this idiom.
In talking about the above code we will call the first function is_valid_settings() the "final wrapper" (or sometimes the "main function') and we call the second function do_is_valid_settings() the "default implementation".
The first thing to note is that the final wrapper is_valid_settings() is declared final and thus cannot be overridden by implementations; and the second thing to note is that the final wrapper declares the data types on its interface.
In contrast the default implementation do_is_valid_settings() is not marked as final, and it does not declare the types on its interface.
This is an example of Postel's Law, which is also known as the Robustness Principle. The final wrapper is liberal in what it accepts, such as with the return value one ( 1 ) from the default implementation; and conservative in what it does, such as always returning a properly typed boolean value and always providing values of the correct type to the default implementation.
Not needing to write out and declare the types on the interface of the default implementation also makes implementation and debugging easier, as there's less code to write. (Also I find the syntax for return types a bit ugly and have a preference for avoiding it when possible, but that's a trivial matter.)
Ordinarily users of this code will only call the main function is_valid_settings() , and anyone implementing new code only needs to override do_is_valid_settings() .
In general you should always wrap any non-final methods (except for private ones) with a final method per this idiom, so that you can have callers override functionality as they may want to do but retain the ability to maintain standards as you may want to do.
If you're refactoring a private method to make it public or protected be sure to introduce the associated final wrapper.
One last thing: if your component has a public function, it should probably be a final wrapper and just defer to a default implementation.
Default implementations should pretty much always be protected, certainly not public, and maybe private if you're not ready to expose the implementation yet.
Having types on the interface of the final method is_valid_settings() confers three main advantages.
The first is that the interface is strongly typed, which means your callers can know what to expect and PHP can take care of fixing up some of the smaller details for us.
The second advantage of this approach is that our final wrapper function is marked as final. This means that the implementer can maintain particular standards within the library and be assured that those standards haven't been elided, accidentally or otherwise.
Having code that you rely on marked as final helps you to reason about the possible states of your component. In the example given above the requirement that $setting_b is less than or equal to 20 bytes in length is a requirement that cannot be changed by implementations; implementations can only make the requirements stronger, such as is done in the default implementation given in the example, where the maximum length is reduced further to 10 bytes.
Another advantage of the typed interface is that it provides extra information which can be automatically added into the documentation. The typed interface communicates intent to the PHP run-time but also to other programmers reading, using, or maintaining the code.
Not having types on the interface of the default implementation do_is_valid_settings() confers four main advantages.
The first is that it's easier to type out and maintain the overriding function as you don't need to worry about writing out the types.
Also, in future, the is_valid_settings() might declare a new interface and change its types. If this happens it can maintain support for both old and new do_is_valid_settings() implementations without implementers necessarily needing to update their code.
The third advantage of an untyped interface for the do_is_valid_settings() function is that it allows for the injection of "impossible" values. These are values which will never be able to make it past the types declared on the main function is_valid_settings() and into the do_is_valid_settings() function, and being able to inject such "impossible" values can make unit testing of particular situations easier, as you can pass in a value that could never possibly occur in production in order to signal something from the test in question.
The fourth and perhaps most important implication of the approach to the default implementation is that it is not marked as final which means that programmers inheriting from your class can provide a new implementation, thereby replacing, or augmenting, the default implementation.
One way a programmer can go wrong is to infinitely recurse. For example like this:
class InfiniteRecursion extends KickassCryptoOpenSslKickassOpenSslRoundTrip {
protected function do_encrypt( $input ) {
return $this->encrypt( $input );
}
}
If the do_encrypt() function calls the encrypt() function, the encrypt() function will call the do_encrypt() function, and then off we go to infinity.
If you do this and you have Xdebug installed and enabled that will limit the call depth to 256 by default. If you don't have Xdebug installed and enabled PHP will just start recurring and will continue to do so until it hits its memory limit or runs out of RAM.
Since there's pretty much nothing this library can do to stop programmers from accidentally writing code like the above what we do is to detect when it's probably happened by tracking how deep our calls are nested using an enter/leave discipline, like this:
try {
$this->enter( __FUNCTION__ );
// 2023-04-07 jj5 - do work...
return $result;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
}
finally {
try { $this->leave( __FUNCTION__ ); } catch ( Throwable $ignore ) { ; }
}
The leave() function has no business throwing an exception, but we wrap it in a try-catch block just in case.
The example code above is shown with typical catch blocks included, but the key point is that the very first thing we do is register the function entry with the call to enter() and then in our finally block we register the function exit with the call to leave() .
If a function enters more than the number of times allowed by KICKASS_CRYPTO_RECURSION_LIMIT without leaving then an exception is thrown in order to break the recursion. At the time of writing KICKASS_CRYPTO_RECURSION_LIMIT is defined as 100, which is less than the Xdebug limit of 256, which means we should always be able to break our own recursive loops.
And for all the trouble we've gone to if the inheritor calls themselves and recurs directly there is nothing to be done:
class EpicFail extends KickassCryptoOpenSslKickassOpenSslRoundTrip {
protected function do_encrypt( $input ) {
return $this->do_encrypt( $input );
}
}
As mentioned above and elaborated on in the following section this library won't usually throw exceptions from the methods on its public interface because we don't want to leak secrets from our call stack if there's a problem.
Instead of throwing exceptions the methods on the classes in this library will usually return false instead, or some other invalid value such as null or [] .
The avoidance of exceptions is only a firm rule for sensitive function calls which handle secret keys, passphrases, unencrypted content, or any other sensitive data. At the time of writing it's possible for the public get_error_list() function to throw an exception if the implementer has returned an invalid value from do_get_error_list() , apart from in that specific and hopefully unlikely situation everything else should be exception safe and use the boolean value false (or another appropriate sentinel value) to communicate errors to the caller.
Sometimes because of the nature of a typed interface it's not possible to return the boolean value false and in some circumstances the empty string ( '' ), an empty array ( [] ), null ( null ), the floating-point value zero ( 0.0 ), or the integer zero ( 0 ) or minus one ( -1 ) may be returned instead; however, returning false is definitely preferred if it's possible.
Aside: in some cases minus one ( -1 ) can be used as the sentinel value to signal an error, such as when you want to indicate an invalid array index or an invalid count, but unlike in some other languages in PHP minus one isn't necessarily an invalid array index, and returning false is still preferred. This library does use minus one in some cases, if there's a problem with managing the telemetry counters.
The fact that an error has occurred can be registered with your component by a call to error() so that if the callers get a false return value they can interrogate your component with a call to get_error() or get_error_list() to get the recent errors (the caller can clear these errors with clear_error() too).
In our library the function for registering that an error has occurred is the error() function defined in the KickassCrypto class.
In some error situations the best and safest thing to do is swallow the error and return a sensible and safe and uncontroversial default value as a fallback.
Here's a quick run-down:
get_error_list() you get an exception with no errorget_error() you get null and an errorclear_error() it's void but with an errorhandle() you get a log entry, no errornotify() it will be handled then ignored, no errorignore() you get a log entry, no errorthrow() it will throw anywayerror() your error may not be properly registered, it always returns falsecount_*() counter you get -1 and no errorincrement_counter() you get -1 and no errorget_const_data_format() you get an empty string and no errorget_const_*() constant accessor you get the value defined by the default constant and no errorget_config_*() config accessor you get the value defined by the default constant (or false if there is no such thing) and no errorget_const() you get the default value and no errorget_passphrase_list() you get an empty array and an errorget_encryption_passphrase() you get null and no erroris_*() method you will get false and no errorget_data_encoding() you will get an empty string and no errorget_data_format() you will get false and no errorconvert_secret_to_passphrase() you will get false and no errorget_padding() you will get false and no errorget_delay() you will get false and no error (an emergency delay will be injected)delay() you will get void and no error (an emergency delay will be injected)log_error() you will get false and no error (but we try to be forgiving)This library is very particular about exception handling and error reporting.
If you have sensitive data on your call stack you must not throw exceptions. Sensitive data includes:
If you encounter a situation from which you cannot continue processing of the typical and expected program logic the way to register this problem is by calling the error() function with a string identifying and describing the problem and then returning false to indicate failure.
As the error() function always returns the boolean value false you can usually register the error and return false on the same like, like this:
return $this->error( __FUNCTION__, 'something bad happened.' );
When I nominate error strings I usually start them with a lowercase letter and end them with a period.
Note that it's okay to intercept and rethrow PHP AssertionError exceptions. These should only ever occur during development and not in production. If you're calling code you don't trust you might not wish to rethrow AssertionError exceptions, but if you're calling code you don't trust you've probably got bigger problems in life.
If you have a strong opinion regarding AssertionError exceptions and think I should not rethrow them I would be happy to hear from you to understand your concern and potentially address the issue.
Following is some example code showing how to handle exceptions and manage errors.
protected final function do_work_with_secret( $secret ) {
try {
$result = str_repeat( $secret, 2 );
$this->call_some_function_you_might_not_control( $result );
return $result;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
}
try {
return $this->error( __FUNCTION__, 'error working with string.' );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
return false;
}
In actual code you would define an error constant for use instead of the string literal 'error working with string.' . In this library the names of error constants begin with "KICKASS_CRYPTO_ERROR_" and they are defined in the src/code/global/constant/framework.php file.
Note that we don't even assume it's safe to call handle() , ignore() , or error() ; we wrap all such calls in try-catch handlers too. There are some edge case situations where even these functions which are supposed to be thread safe can lead to exceptions, such as when there's infinite recursion which gets aborted by the run-time. If you're an expert on such matters the code might do with a review from you.
Now I will agree that the above code is kind of insane, it's just that it seems to me like there's no avoiding it if we want to be safe. We have to explicitly allow the AssertionError exception every single time in every single method just so that assertions remain useful to us as a development tool, and then when we handle other exceptions we want to make some noise about them so we call handle() , but the thing is that handle() will defer to do_handle() which can be overridden by implementers, which means it can throw... so if handle() throws we don't want to just do nothing, we want to give the programmer a last chance to learn of their errant code, so we notify that we're going to ignore the exception with a call to ignore() , but that will defer to do_ignore() , which the programmer could override, and throw from... but if that happens we will just silently ignore such a problem.
And then if we get through all of that and our function hasn't returned then that's an error situation so we want to notify the error, but error() defers to do_error() and that could be overridden and throw, so we wrap in a try-catch block and then do the exception ignore dance again.
I mean it's all over the top and excessive but it should at least be safe and it meets two requirements:
In the usual happy code path none of the exception handling code even runs.
There are a bunch of functions for testing boolean conditions, and they begin with "is_" and return a boolean. These functions should only do the test and return true or false, they should not register errors using the error() function, if that's necessary the caller will do that.
The is_() functions can be implemented using the typed final wrapper idiom documented above.
Following is a good example from the code.
protected final function is_valid_secret( $secret ) : bool {
try {
$is_valid = $this->do_is_valid_secret( $secret );
// ...
assert( is_bool( $is_valid ) );
return $is_valid;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
}
return false;
}
Note that do_is_valid_secret() also has a secret on the call stack, so it should be implemented as exception safe in the same way (in case it is called directly from some other part of the code).
Note too that it's okay to just rethrow assertion violations, these should never happen in production and they make testing the code easier.
The approach to unit-testing taken by this library is simple and powerful. There are three types of test which can be defined for each unit test:
Each script will be either a shell script with the same name, eg fast.sh , or if that's missing a PHP script with the same name, eg fast.php . The test runner just finds these scripts and runs them. This is easy to do and provides all the power we need to run our tests, including support for the various situations where each test instance needs to run in its own process and be isolated from other testing environments.
If you have flakey and unreliable tests you can stick them in as silly tests. The fast and slow tests are the important ones, and you shouldn't put slow tests in the fast test scripts. The fast tests are for day to day programming and testing and the slow scripts are for running prior to a version release.
Here are some notes regarding notable components:
config.php file for the demoSome countries have banned the import or use of strong cryptography, such as 256 bit AES.
Please be advised that this library does not contain cryptographic functions, they are provided by your PHP implementation.
Copyright (c) 2023 John Elliot V.
This code is licensed under the MIT License.
See the contributors file.
I should probably be more disciplined with my commit messages... if this library matures and gets widely used I will try to be more careful with my commits.
The Kickass Crypto ASCII banner is in the Graffiti font courtesy of TAAG.
The string "kickass" appears in the source code 1,506 times (including the ASCII banners).
SLOC and file count reports generated using David A. Wheeler's 'SLOCCount'.
Eu adoraria ouvir de você! Hit me up at [email protected]. Put "Kickass Crypto" in the subject line to make it past my mail filters.