Domanda Implementazione personale cifrario di Cesare

--- Ra ---

Utente Emerald
24 Dicembre 2021
834
21
506
371
Ultima modifica:
Ciao ragazzi, come da titolo, ho provato a fare una mia implementazione del cifrario di Cesare, per puro diletto. Il codice è funzionante e l'ho testato con compilatori diversi (anche se non serviva dato che la codifica ASCII è sempre la stessa) e sembra sempre terminare l'esecuzione correttamente. Potreste darci un' occhiata per verificare che non ci siano potenziali errori di cui non mi sono accorto? Ho utilizzato gli indici della tabella ASCII per effettuare la conversione dei caratteri: non si dovrebbe fare per evitare errori imprevisti, ma credo di aver adottato, nel codice, le contromisure adeguate affinché questo non accada. Posto di seguito il programma, grazie a chi mi risponderà!

C++:
/*
    PROGRAM NAME: enCODER
    SCOPE: The following program encrypts a text using the Caesar cipher
    and the numerical values of the characters corresponding to the ASCII table.
    DATE: 10/10/2023
    AUTHOR: CrazyMonk
    USAGE: Enter the cipher key (a number > 0) and a message (only letters) separated by a space.
 */

#include <iostream>
#include <string>

using namespace std;

const int INDEX_Z = 122;
const int INDEX_A = 97;
const int INDEX_SPACE = 32;

string convertToLowercase(string phrase);
string encryptPhrase(string phrase, int cipherKey);
void keyValidation(int &key, int min, int max);

int main() {
    string phrase;
    int cipherKey;
   
    cout << "\nInsert a key between 1 and 25 to encrypt: ";
    cin >> cipherKey;
    keyValidation(cipherKey, 1, 25);
    cout << "\n Enter the phrase to encrypt: ";
    cin.ignore();
    getline(cin, phrase);
    string finalString = convertToLowercase(phrase);
    cout << "\nThe encrypted phrase is:" << encryptPhrase(phrase, cipherKey) << endl;

    return 0;
}

string convertToLowercase(string phrase){
    int stringLength = phrase.length();

    if (stringLength == 0){
        return "ERROR! EMPTY STRING!";
    }
    for (int i = 0; i < stringLength; i++){
        phrase[i] = tolower(phrase[i]);
    }

    return phrase;
}

string encryptPhrase(string phrase, int cipherKey){
    int stringLength = phrase.length();

    for (int i = 0; i < stringLength; i++){
        if ((int(phrase[i]) + cipherKey) > INDEX_Z){ // Verifico che il carattere non fuoriesca dall'alfabeto
            int finalCharIndex = (int)phrase[i] + cipherKey; //Calcolo quale sarebbe l'indice del carattere fuoriuscito
            int difference = finalCharIndex - INDEX_Z; //Calcolo la differenza tra l'indice del carattere fuoriuscito e l'indice dell'ultima lettere dell'alfabeto minuscolo (z)
            phrase[i] = (INDEX_A - 1) + difference;    //Con la differenza precedentemente calcolata, torno all'inizio dell'alfabeto ed effettuo l'incremento rimanente
            continue;
        }

        if (phrase[i] == INDEX_SPACE){
            continue;
        }
        phrase[i] += cipherKey;
    }
    return phrase;
}

void keyValidation(int &key, int min, int max){
    while (key < min || key > max){
        cout << "ERROR. Insert a key between " << min << " and " << max << ": ";
        cin >> key;
    }
}
 
Ciao, da un'occhiata veloce non mi sembra che ci siano errori; in ogni caso al fine di snellire un po' il codice farei qualcosa del genere:

C++:
#include <iostream>
#include <string>

using namespace std;

void convertToLowercase(string &s)
{
    for(unsigned int length = s.size(), i = 0; i < length; ++i)
    {
        s[i] = tolower(s[i]);
    }
}

void encryptPhrase(string &s, unsigned int key)
{
    for(unsigned int length = key ? s.size() : 0, i = 0; i < length; ++i)
    {
        if(s[i] >= 'a' && s[i] <= 'z' && (s[i] += key) > 'z')
        {
            s[i] = 'a' + s[i] - 'z' - 1;
        }
    }
}

int main()
{
    string s;
    unsigned int key;

    cout << "INPUT:\t";
    getline(cin, s);
    convertToLowercase(s);

    cout << "KEY:\t";
    cin >> key;
    key %= 'z' - 'a' + 1;

    encryptPhrase(s, key);
    cout << "OUTPUT:\t" << s << endl;
}
 
  • Love
Reazioni: --- Ra ---
@Super Squirrel, grazie mille della risposta. Ho compreso la tua soluzione, ma ho solo un dubbio. Quando scrivi:

C++:
s[i] = 'a' + s[i] - 'z' - 1;

In pratica stai tornando indietro nell'alfabeto se l'incremento supera l'indice di 'z'. Scrivendo s - 'z' calcoliamo la differenza tra l'indice del carattere corrente della stringa e quello di 'z'. Quando sottraiamo il valore 1 stiamo tornando al valore prima di 'a' nel caso in cui 'z' ed s corrispondano, corretto?
 
Posto anche la mia soluzione
C++:
#include <iostream>
#include <string>
using namespace std;

// Euclidean modulus (not the reminder).
int mod(int n, int m) { return ((n % m) + m) % m; }

// Rotate x by k and keep the result in the range [from, to].
int rotate(int x, int k, int from, int to) {
  return from + mod((x - from + k), (to - from + 1));
}

string encrypt(string text, int key) {
  for (auto &c : text) {
    if ('a' <= c && c <= 'z') c = rotate(c, key, 'a', 'z');
    else if ('A' <= c && c <= 'Z') c = rotate(c, key, 'A', 'Z');
    else if ('0' <= c && c <= '9') c = rotate(c, key, '0', '9');
  }
  return text;
}

string decrypt(string text, int key) { return encrypt(move(text), -key); }

int main() {
  int key;
  cout << "Insert key: ";
  cin >> key;

  string plaintext;
  cout << "Insert text: ";
  cin.ignore();
  getline(cin, plaintext);

  string ciphertext = encrypt(plaintext, key);
  cout << "Encrypted: " << ciphertext << endl;
  cout << "Decrypted: " << decrypt(ciphertext, key) << endl;
  return 0;
}

In particolare ti faccio notare che nella mia soluzione puoi usare numeri negativi o numeri più grandi di 25 come chiave. Non aumenta la sicurezza perché, per esempio, usare 27 equivale ad usare 1, però forse è un po' più comodo a livello pratico. Ho anche scelto di cifrare separatamente le lettere maiuscole, quelle minuscole e i numeri.
 
  • Love
Reazioni: --- Ra ---
Fenomenale @St3ve! Sapevo che avresti dato il tuo contributo, molto molto interessante, come al solito. ♥️ La tua soluzione mi ha flashato un botto: molto ingegnosa l'idea di utilizzare il modulo di Euclide, ma è per caso una cosa standard che si fa quando si vuole fare variare un certo valore entro un range ben definito? L'unica cosa che non ho capito è una funzione che hai usato, ossia move(). In che libreria sta? A che serve?
 
La tua soluzione mi ha flashato un botto: molto ingegnosa l'idea di utilizzare il modulo di Euclide, ma è per caso una cosa standard che si fa quando si vuole fare variare un certo valore entro un range ben definito?
È il modo matematicoso per spiegare il cifrario di Cesare. Data una chiave $$\(k \in \mathbb{Z}$$\) e un alfabeto finito $$\(\Sigma = \{\sigma_0, \sigma_1, \ldots, \sigma_{n-1}\}$$\) definiamo
$$\[c = p + k \mod n \qquad\qquad p = c - k \mod n$$\]e diciamo che il carattere $$\(\sigma_p$$\) cifrato diventa $$\(\sigma_c$$\) e, vice versa, $$\(\sigma_c$$\) deficrato diventa $$\(\sigma_p$$\).

In parole povere, se stiamo manipolando i numeri da 0 a 25 (che rappresentano le ventisei lettere che vanno dalla A alla Z) possiamo cifrare il numero p calcolando $$\(p + k \bmod 26$$\) e possiamo decifrare il numero c calcolando $$\(c - k \bmod 26$$\). Questo vale per qualsiasi k intero, anche negativo. In C++ l'operatore modulo % calcola il resto della divisione, ma se non vogliamo risultati inaspettati abbiamo bisogno del modulo vero (quello di Euclide) che si può calcolare come puoi leggere nel codice che ho postato.

Se sei interessato a smanettare un po' di più con l'aritmetica modulare, senza fare cose troppo difficili, puoi provare a implementare il cifrario affine. Al posto di usare una chiave per fare $$\(c = p + k \bmod n$$\) usa due chiavi e calcola $$\(c = k_1 p + k_2 \bmod n$$\), ma non tutte le chiavi sono consentite perché altrimenti potresti ritrovarti con qualcosa di matematicamente indecifrabile.

L'unica cosa che non ho capito è una funzione che hai usato, ossia move(). In che libreria sta? A che serve?
È una funzione che è stata introdotta in C++11 ed è semplicemente un cast a r-value reference. La funzione encrypt si copia tutta la stringa, modifica questa copia e poi la restituisce. La funzione decrypt è sempre pass-by-value, quindi anche lei si copia tutta la stringa, però poi la rigira (move) alla funzione encrypt senza fare nessun altra copia (copy elision). Il codice funziona anche se rimuovi move, ma se la lasci l'uso della memoria è un po' più ottimizzato.
 
  • Love
Reazioni: --- Ra ---