This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Blog

This is the blog section. It has two categories: News and Releases.

Files in these directories will be listed in reverse chronological order.

News

Implementación de Estándares Criptográficos PKCS en Rust 🔒

Explora la implementación de estándares criptográficos PKCS en Rust, incluyendo ejemplos prácticos de generación y gestión de claves privadas usando algoritmos como Ed25519 y Secp256k1.

¿Qué Son los PKCS?

Los PKCS son un conjunto de estándares desarrollados por RSA Laboratories para facilitar la implementación de criptografía de clave pública. Estos estándares abordan diferentes aspectos de la criptografía, desde el cifrado y la firma digital hasta la gestión de claves y el intercambio seguro de datos.

Alguno de los estándares más utilizados:

PKCS Código Extensión Descripción
PKCS#1 RFC 8017 .pem, .der Especifica la sintaxis para RSA encriptación. Define esquemas de cifrado RSA, y formatos de firma digital.
PKCS#3 RFC 5246 .pem, .der Protocolo de generación de clave Diffie-Hellman. Utilizado para el intercambio seguro de claves.
PKCS#5 RFC 8018 .pem, .der Estándar para cifrar contraseñas. Define métodos para derivar claves a partir de contraseñas.
PKCS#7 RFC 5652 .p7b, .p7c, .p7s Formato de mensaje criptográfico. Utilizado para almacenar datos firmados o cifrados.
PKCS#8 RFC 5958 .pem, .der, .p8 Formato de clave privada. Define la sintaxis para almacenar claves privadas encriptadas y no encriptadas.
PKCS#10 RFC 2986 .csr Solicitud de certificado. Utilizado para solicitar la emisión de un certificado por una autoridad de certificación (CA).
PKCS#11 OASIS PKCS#11 N/A API para manejar objetos criptográficos en dispositivos de hardware. Utilizado en módulos de seguridad de hardware (HSM).
PKCS#12 RFC 7292 .p12, .pfx Contenedor de objetos criptográficos. Utilizado para almacenar claves privadas, certificados, y otros secretos.
PKCS#13 ISO/IEC 18033-2 N/A Algoritmos de encriptación de clave pública. Define esquemas para encriptación basada en RSA.
PKCS#15 ISO/IEC 7816-15 N/A Armazón de objetos criptográficos en tarjetas inteligentes. Define estructuras para almacenar certificados y claves en tarjetas inteligentes.

Implementación

Haremos uso de una de las librerias más famosas de criptografía en Rust, Rust Crypto. Gracias a esta disponemos de una amplia variedad de algoritmos para realizar las pruebas, además de disponer de soporte para diferentes formatos de codificación como der(binario) y pem(base64).

Generación de un par de claves

Crearemos una función que nos permita genera un par de claves(privada y publica) en base a un algoritmo. Utilizaremos dos algoritmos de curva elíptica utilizados en criptografía de clave pública:

  • Ed25519, ampliamente recomendado por su robustez y eficiencia.
  • Secp256k1, Aunque no está tan estandarizado como Ed25519, su uso en Bitcoin ha llevado a su implementación en varias bibliotecas de criptografía.

Para ejecutar los ejemplos haremos uso de la libreria identitydisponible en el repositorio de kore-ledger ya que en esta libreria tenemos una estructura para trabajar con estos algoritmos.

use kore_base::keys::{Ed25519KeyPair, KeyGenerator, KeyMaterial, KeyPair, Secp256k1KeyPair};
enum Algorithm {
    Ed25519,
    Secp256k1,
}
fn generate_keypair(algorithm: Algorithm) -> KeyPair {
    let kp = match algorithm {
        Algorithm::Ed25519 => {
            let keys = Ed25519KeyPair::from_seed(&[])
            KeyPair::Ed25519(keys);
        }
        Algorithm::Secp256k1 => {
            let keys = Secp256k1KeyPair::from_seed(&[])
            KeyPair::Secp256k1(keys);
        }
    };
    kp
}

Escritura en formato der

Generaremos un directorio con las claves en formato der, en este caso guardaremos las claves en pkcs8 encriptadas con pkcs5 al igual que las claves que reciben los nodos Kore Ledger.

fn write_keys(
    secret_key: &[u8],
    public_key: &[u8],
    password: &str,
    path: &str,
    algorithm: Algorithm,
) -> Result<(), Error> {
    // Creamos un directorio
    match fs::create_dir_all(path) {
        Ok(_) => {
            match algorithm {
                Algorithm::Ed25519 => {
                  
                    let mut keypair_bytes: Vec<u8> = Vec::new();
                    keypair_bytes.extend_from_slice(secret_key);
                    keypair_bytes.extend_from_slice(public_key);
                    // Clave privada y pública de 32 bytes
                    let keypair_bytes_array: [u8; 64] = keypair_bytes
                        .try_into()
                        .map_err(|_| "Error al convertir Vec<u8> a [u8; 64]")?;

                    let signing_key = ed25519::KeypairBytes::from_bytes(&keypair_bytes_array);
                    let public_key = signing_key.public_key.unwrap();
                    // Convertimos a pkcs8 en formato der(binario)
                    let der = match signing_key.to_pkcs8_der() {
                        Ok(der) => der,
                        Err(e) => {
                            return Err(format!("Error converting to PKCS8 DER: {}", e));
                        }
                    };

                    let pk_encrypted =
                        encrypt_from_pkcs8_to_pkcs5(der.as_bytes(), password).unwrap();

                    // Private key in pkcs8 encrypted with pkcs5
                    pk_encrypted
                        .write_der_file(format!("{}/private_key.der", path))
                        .map_err(|e| format!("Error writing private key to file: {}", e))?;

                    // Public key in pksc8
                    public_key
                        .write_public_key_der_file(format!("{}/public_key.der", path))
                        .map_err(|e| format!("Error writing public key to file: {}", e))
                        .unwrap();
                }
                Algorithm::Secp256k1 => {
                    let sec1_key: Sec1SecretKey<k256::Secp256k1> =
                        Sec1SecretKey::from_slice(secret_key).unwrap();

                    let sec1_public_key = sec1_key.public_key();
                    let sec1_der = sec1_key
                        .to_pkcs8_der()
                        .map_err(|e| format!("Error converting to PKCS8 DER: {}", e))
                        .unwrap();

                    let pk_encrypted =
                        encrypt_from_pkcs8_to_pkcs5(sec1_der.as_bytes(), password).unwrap();

                    // Private key in pkcs8 encrypted with pkcs5
                    pk_encrypted
                        .write_der_file(format!("{}/private_key.der", path))
                        .map_err(|e| format!("Error writing private key to file: {}", e))
                        .unwrap();

                    // Public key in pksc8
                    sec1_public_key
                        .write_public_key_der_file(format!("{}/public_key.der", path))
                        .map_err(|e| format!("Error writing public key to file: {}", e))
                        .unwrap();
                }
            }
            Ok(())
        }
        Err(e) => Err(format!("Error creating directory: {}", e)),
    }
}

Escriptación de pkcs8 con pksc5

Necesitamos una función que encripte el pkcs8 a pkcs5.

fn encrypt_from_pkcs8_to_pkcs5(
    sec1_der_bytes: &[u8],
    password: &str,
) -> Result<pkcs8::SecretDocument, String> {
    let pbes2_params = match pkcs5::pbes2::Parameters::pbkdf2_sha256_aes256cbc(
        2048,
        &hex!("79d982e70df91a88"),
        &hex!("b2d02d78b2efd9dff694cf8e0af40925"),
    ) {
        Ok(pbes2_params) => pbes2_params,
        Err(e) => {
            return Err(format!("Error creating PBES2 parameters: {}", e));
        }
    };
    let pk_text = match PrivateKeyInfo::try_from(sec1_der_bytes) {
        Ok(pk_text) => pk_text,
        Err(e) => {
            return Err(format!("Error creating PrivateKeyInfo: {}", e));
        }
    };
    let pk_encrypted = match pk_text.encrypt_with_params(pbes2_params, password) {
        Ok(pk_encrypted) => pk_encrypted,
        Err(e) => {
            return Err(format!("Error encrypting private key: {}", e));
        }
    };
    Ok(pk_encrypted)
}

### Lectura de Claves Privadas en Formato DER

Las claves privadas en formato DER pueden ser leídas de dos maneras distintas dependiendo de si están encriptadas o no:

fn read_der_file(
    path: Option<&str>,
    typefile: Typefile,
    algorithm: Algorithm,
    password: Option<&str>,
) -> Result<KeyPair, String> {
    let path = path.unwrap();
    // Verificamos si existe la ruta
    if fs::metadata(path).is_ok() {
        match typefile {
            // Debemos distinguir los der de claves públicas sin encriptación y los de clave privada
            Typefile::PrivateKey => {
                let document = Document::read_der_file(path)
                    .map_err(|e| format!("Error reading file: {}", e))
                    .unwrap();
                match EncryptedPrivateKeyInfo::try_from(document.as_bytes()) {
                    Ok(enc_pk_info) => {
                        if password.is_none() {
                            return Err("Password is required to decrypt".into());
                        }
                        // Desencriptamos el archivo
                        let der_private_key = enc_pk_info
                            .decrypt(password.unwrap())
                            .map_err(|e| format!("Error decrypting file: {}", e))
                            .unwrap();
                        // Obtenemos nuestro par de claves
                        let decode_private_key: ed25519::KeypairBytes = ed25519::pkcs8::KeypairBytes::from_pkcs8_der(document)
                        .map_err(|e| {
                        format!("Error creating KeypairBytes(ED25519) from PKCS8 DER: {}", e)
                        })
                        .unwrap();
                    }
                    Err(_) => {
                      // Obtenemos nuestro par de claves
                        let decode_private_key: ed25519::KeypairBytes = ed25519::pkcs8::KeypairBytes::from_pkcs8_der(document)
                        .map_err(|e| {
                        format!("Error creating KeypairBytes(ED25519) from PKCS8 DER: {}", e)
                        })
                        .unwrap();
                    }
                }
            }
        }
    } else {
        Err("File not found".into())
    }
}

Flutter Rust Bridge 🦀 A History of Innovation and Challenges

A guide on how to get started with Flutter Rust Bridge and the advantages it offers for application development.

Introduction

A few years ago, in a corner of the technology world, a group of visionary developers sought to combine the best of two worlds: the power and security of Rust with the versatility of Flutter. Thus was born Flutter Rust Bridge, a tool that breaks new ground for developers who want to integrate Rust code into Flutter applications.

The story begins with the need to leverage the advantages of both languages. Rust, known for its performance and memory safety, promised a bright future, while Flutter offered the ability to create attractive and efficient user interfaces. Together, these two languages could take application development to a new level.

How to use Flutter with Rust?

The First Steps

The journey begins with installing the prerequisites, first you need to have Flutter and Rust installed on your machine. You can find the installation instructions at the following links:

The Creation of the Project

With the foundation in place, developers take the next step: starting a new project. This is where the magic really begins. To start the project we must run the following command that will allow us to install the flutter_rust_bridge_codegen library responsible among other things for performing the conversion from rust to dart.

cargo install 'flutter_rust_bridge_codegen@^2.0.0-dev.0' && flutter_rust_bridge_codegen create my_app && cd my_app

Next we will proceed to create our first function in rust, in this case we are going to consume an endpoint that allows the registration of a user. The path where we will write the function is rust/src/request.rs.

#[derive(Serialize, Clone)]
pub struct UserAuth {
  pub username: String,
  pub password: String, 
}
pub async fn register(username: String, password: String) -> Result<String, String> {
    let body = UserAuth { password, username };
    let client = reqwest::Client::new();
    match client
        .post("http://localhost:3030/register")
        .json(&body)
        .send()
        .await
    {
        Ok(res) => {
            let fail = !res.status().is_success();
            match res.json().await {
                Ok(username_or_error) => {
                    if fail {
                        Err(username_or_error)
                    } else {
                        Ok(username_or_error)
                    }
                }
                Err(e) => Err(format!("{}", e)),
            }
        }
        Err(e) => Err(format!("{}", e)),
    }
}

To transform the rust code to dart code we must execute the following command:

flutter_rust_bridge_codegen generate

Note that only the types that have been used in the functions will be converted. You should create the following in the path rust/src/request.dart.

Future<String> register(
        {required String username, required String password, dynamic hint}) =>
    RustLib.instance.api
        .register(username: username, password: password, hint: hint);

Future<String> register(
      {required String username, required String password, dynamic hint}) {
    return handler.executeNormal(NormalTask(
    //  Rust function call
      callFfi: (port_) {
        final serializer = SseSerializer(generalizedFrbRustBinding);
        // Data are serialized
        sse_encode_String(username, serializer);
        sse_encode_String(password, serializer);
        pdeCallFfi(generalizedFrbRustBinding, serializer,
            funcId: 7, port: port_);
      },
      codec: SseCodec(
        decodeSuccessData: sse_decode_String,
        decodeErrorData: sse_decode_String,
      ),
      constMeta: kRegisterConstMeta,
      argValues: [username, password],
      apiImpl: this,
      hint: hint,
    ));
  }

class UserAuth {
  final String username;
  final String password;

  const ResUsersPag({
    required this.username,
    required this.password,
  });

  @override
  int get hashCode =>
      username.hashCode ^ password.hashCode;
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ResUsersPag &&
          runtimeType == other.runtimeType &&
          username == other.username &&
          password == other.password &&
}

We can see how a function has been created transforming the async to Future in dart and a task is created that makes a call to the rust(callFfi) function taking a port, defined in _port. Within the call the arguments of the user name and password are passed. Subsequently the struct has been transformed to a class with attributes of type String. In addition, a getter is performed to obtain the class data and an overload of the == operator.

Finally from our flutter application we can import and use this function.

import 'package:app/components/request.dart';

register(username: usernameController.text,password: passwordController.text)
    .then((response) {
    }).catchError((error) {
    }).whenComplete(() {
    });

Construction

In case of making a development deployment we will have to execute the following commands:

flutter_rust_bridge_codegen build-web 
# De esta manera evitaremos los problemas de Cors. 
flutter run --web-header=Cross-Origin-Opener-Policy=same-origin --web-header=Cross-Origin-Embedder-Policy=require-corp

To realize the final construction

flutter build web 

In case of a build for applications

flutter build [macos|ios|apk|window]

Architecture

The Flutter Rust Bridge architecture is designed to provide cross-platform compatibility and a seamless connection between Dart functionality in Flutter and Rust code.

The external function interface is essential to this architecture, as it allows one language to call code from another language. In our case, Dart FFI is used to access Rust functions from a Flutter application.

In this framework, we start from a Rust library and create a link to expose Rust functions to external code, in this case, through Rust FFI. From the Flutter side, you use Dart FFI to reach the native layer and call the appropriate Rust functions. In this way, you can integrate native functionality into your Flutter application using Rust code.

Integrating Rust into Flutter through the Flutter Rust Bridge results in natively compiled applications with performance on par with native code. This solution allows you to leverage the strengths of Rust on a variety of supported platforms, including Android, iOS, Windows, Linux and even web, thus providing more efficient cross-platform mobile development.

Threading

Flutter provides support for thread handling through different mechanisms depending on the target platform. The following details how to handle threads in different Flutter environments:

Web

When it comes to Flutter applications intended for the web, threads are handled using Web Workers. These allow background tasks to be performed to avoid blocking the user interface (UI), improving the efficiency and responsiveness of the application.

Native

For Flutter applications running on native platforms, thread handling is done using Isolates. The Isolates are a Dart-independent form of execution that can execute its own code and memory.

Although different techniques have to be performed depending on the target platform, it is possible to use libraries such as isolated_worker that allow within the same program to detect the execution by Isolated or Web Workers depending on the platform.

Advantages

  • Improved performance: Rust is known for its high performance and memory safety. By using Rust for application logic, you can improve the performance of your Flutter application.

  • Safe code: Rust has memory safety features that help avoid common errors such as buffer overflows and race conditions.

  • Shared code: You can write shared logic in Rust and use it both in the Flutter application and on other platforms, making cross-platform development easy.

  • Recent updates: The Flutter Rust Bridge community is constantly adding issues and updates to improve the features of the library. Recent updates include support for the str type in dart.

Drawbacks

  • Learning curve: Learning Rust can be a challenge, especially for developers new to the language.

  • Smaller community: Although Rust has an active community, it is smaller compared to other languages such as JavaScript or Python. This could make it more difficult to find examples or documentation specific to Flutter integration.

  • Types: Currently not all Rust types are supported in Dart.

Conclusion

In conclusion, Flutter Rust Bridge is a powerful tool that allows you to integrate Rust code into Flutter applications, combining the efficiency and security of Rust with the versatility and user interface creation capabilities of Flutter. This integration can significantly improve application performance and provides secure, reusable code.

Despite the benefits, it is important to keep in mind some challenges, such as Rust’s learning curve and limitations in the community and supported data types. However, with careful planning and a well-structured development approach, these disadvantages can be overcome.

In the end, Flutter Rust Bridge offers developers a unique opportunity to create modern, powerful and secure applications, taking advantage of the best of both worlds: Rust and Flutter. With practice and experience, developers can learn to use this tool effectively and achieve excellent results in their projects.

Coming together is a beginning. Keeping together is progress. Working together is success. — Henry Ford