mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-11 17:19:29 +00:00
Refactor KeeLoq crypto functions and add new KeeLoq TX app (#2990)
This commit is contained in:
@@ -213,6 +213,7 @@ set(CPPSRC
|
|||||||
irq_controls.cpp
|
irq_controls.cpp
|
||||||
irq_lcd_frame.cpp
|
irq_lcd_frame.cpp
|
||||||
irq_rtc.cpp
|
irq_rtc.cpp
|
||||||
|
keeloq_common.cpp
|
||||||
keeloq_file.cpp
|
keeloq_file.cpp
|
||||||
keeloq_keystore.cpp
|
keeloq_keystore.cpp
|
||||||
log_file.cpp
|
log_file.cpp
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "ui_textentry.hpp"
|
#include "ui_textentry.hpp"
|
||||||
#include "../keeloq_keystore.hpp"
|
#include "../keeloq_keystore.hpp"
|
||||||
#include "../keeloq_file.hpp"
|
#include "../keeloq_file.hpp"
|
||||||
|
#include "../keeloq_common.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
@@ -82,7 +83,7 @@ void SubGhzDRecentEntryDetailView::update_data() {
|
|||||||
KeeloqData params{
|
KeeloqData params{
|
||||||
mf_name,
|
mf_name,
|
||||||
serial,
|
serial,
|
||||||
cnt,
|
(uint16_t)cnt,
|
||||||
btn};
|
btn};
|
||||||
|
|
||||||
ensure_directory(keeloq_remotes_dir);
|
ensure_directory(keeloq_remotes_dir);
|
||||||
@@ -361,28 +362,6 @@ bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt_centurion(uint32_t decry
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t keeloq_decrypt(const uint32_t data, const uint64_t key) {
|
|
||||||
uint32_t x = data, r;
|
|
||||||
for (r = 0; r < 528; r++)
|
|
||||||
x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^
|
|
||||||
bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key) {
|
|
||||||
uint32_t k1, k2;
|
|
||||||
|
|
||||||
data &= 0x0FFFFFFF;
|
|
||||||
data |= 0x20000000;
|
|
||||||
k1 = keeloq_decrypt(data, key);
|
|
||||||
|
|
||||||
data &= 0x0FFFFFFF;
|
|
||||||
data |= 0x60000000;
|
|
||||||
k2 = keeloq_decrypt(data, key);
|
|
||||||
|
|
||||||
return ((uint64_t)k2 << 32) | k1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t came_twee_magic_numbers_xor[15] = {
|
const uint32_t came_twee_magic_numbers_xor[15] = {
|
||||||
0x0E0E0E00,
|
0x0E0E0E00,
|
||||||
0x1D1D1D11,
|
0x1D1D1D11,
|
||||||
|
|||||||
@@ -31,12 +31,6 @@
|
|||||||
#define SD_NO_FIX 0xFFFFFFFF
|
#define SD_NO_FIX 0xFFFFFFFF
|
||||||
#define SD_NO_ENCRYPTED 0xFFFFFFFF
|
#define SD_NO_ENCRYPTED 0xFFFFFFFF
|
||||||
|
|
||||||
#define bit(x, n) (((x) >> (n)) & 1)
|
|
||||||
#define g5(x, a, b, c, d, e) \
|
|
||||||
(bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16)
|
|
||||||
|
|
||||||
#define KEELOQ_NLF 0x3A5C742E
|
|
||||||
|
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ set(EXTCPPSRC
|
|||||||
external/morseradiotx/main.cpp
|
external/morseradiotx/main.cpp
|
||||||
external/morseradiotx/ui_morse_radiotx.cpp
|
external/morseradiotx/ui_morse_radiotx.cpp
|
||||||
|
|
||||||
|
external/keeloqtx/main.cpp
|
||||||
|
external/keeloqtx/ui_keeloqtx.cpp
|
||||||
#rtty_rx
|
#rtty_rx
|
||||||
external/rtty_rx/main.cpp
|
external/rtty_rx/main.cpp
|
||||||
external/rtty_rx/ui_rtty_rx.cpp
|
external/rtty_rx/ui_rtty_rx.cpp
|
||||||
@@ -375,6 +377,7 @@ set(EXTAPPLIST
|
|||||||
siggen
|
siggen
|
||||||
morse_radio
|
morse_radio
|
||||||
morseradiotx
|
morseradiotx
|
||||||
|
keeloqtx
|
||||||
rtty_rx
|
rtty_rx
|
||||||
rtty_tx
|
rtty_tx
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-5
@@ -94,8 +94,9 @@ MEMORY
|
|||||||
ram_external_app_morse_radio (rwx) : org = 0xADF50000, len = 32k
|
ram_external_app_morse_radio (rwx) : org = 0xADF50000, len = 32k
|
||||||
ram_external_app_waterfall_designer (rwx) : org = 0xADF60000, len = 32k
|
ram_external_app_waterfall_designer (rwx) : org = 0xADF60000, len = 32k
|
||||||
ram_external_app_morseradiotx (rwx) : org = 0xADF70000, len = 32k
|
ram_external_app_morseradiotx (rwx) : org = 0xADF70000, len = 32k
|
||||||
ram_external_app_rtty_rx (rwx) : org = 0xADF80000, len = 32k
|
ram_external_app_keeloqtx (rwx) : org = 0xADF80000, len = 32k
|
||||||
ram_external_app_rtty_tx (rwx) : org = 0xADF90000, len = 32k
|
ram_external_app_rtty_rx (rwx) : org = 0xADF90000, len = 32k
|
||||||
|
ram_external_app_rtty_tx (rwx) : org = 0xADFA0000, len = 32k
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTIONS
|
SECTIONS
|
||||||
@@ -528,6 +529,11 @@ SECTIONS
|
|||||||
*(*ui*external_app*morseradiotx*);
|
*(*ui*external_app*morseradiotx*);
|
||||||
} > ram_external_app_morseradiotx
|
} > ram_external_app_morseradiotx
|
||||||
|
|
||||||
|
.external_app_keeloqtx : ALIGN(4) SUBALIGN(4)
|
||||||
|
{
|
||||||
|
KEEP(*(.external_app.app_keeloqtx.application_information));
|
||||||
|
*(*ui*external_app*keeloqtx*);
|
||||||
|
} > ram_external_app_keeloqtx
|
||||||
.external_app_rtty_rx : ALIGN(4) SUBALIGN(4)
|
.external_app_rtty_rx : ALIGN(4) SUBALIGN(4)
|
||||||
{
|
{
|
||||||
KEEP(*(.external_app.app_rtty_rx.application_information));
|
KEEP(*(.external_app.app_rtty_rx.application_information));
|
||||||
@@ -539,8 +545,5 @@ SECTIONS
|
|||||||
KEEP(*(.external_app.app_rtty_tx.application_information));
|
KEEP(*(.external_app.app_rtty_tx.application_information));
|
||||||
*(*ui*external_app*rtty_tx*);
|
*(*ui*external_app*rtty_tx*);
|
||||||
} > ram_external_app_rtty_tx
|
} > ram_external_app_rtty_tx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 lifegame1lu111
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_keeloqtx.hpp"
|
||||||
|
#include "external_app.hpp"
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_keeloqtx {
|
||||||
|
void initialize_app(ui::NavigationView& nav) {
|
||||||
|
nav.push<KeeloqTXView>();
|
||||||
|
}
|
||||||
|
} // namespace ui::external_app::ui_keeloqtx
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
__attribute__((section(".external_app.app_keeloqtx.application_information"), used)) application_information_t _application_information_keeloqtx = {
|
||||||
|
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||||
|
/*.externalAppEntry = */ ui::external_app::ui_keeloqtx::initialize_app,
|
||||||
|
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||||
|
/*.app_version = */ VERSION_MD5,
|
||||||
|
/*.app_name = */ "KeeLoq TX",
|
||||||
|
/*.bitmap_data = */ {
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x30,
|
||||||
|
0x0C,
|
||||||
|
0x30,
|
||||||
|
0x0C,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0x70,
|
||||||
|
0x0D,
|
||||||
|
0xB0,
|
||||||
|
0x0E,
|
||||||
|
0x70,
|
||||||
|
0x0D,
|
||||||
|
0xB0,
|
||||||
|
0x0E,
|
||||||
|
0xF0,
|
||||||
|
0x0F,
|
||||||
|
0xE0,
|
||||||
|
0x07,
|
||||||
|
},
|
||||||
|
/*.icon_color = */ ui::Color::yellow().v,
|
||||||
|
/*.menu_location = */ app_location_t::TX,
|
||||||
|
/*.desired_menu_position = */ -1,
|
||||||
|
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
|
||||||
|
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#include "ui_keeloqtx.hpp"
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "../../keeloq_common.hpp"
|
||||||
|
#include "../../baseband/fprotos/fprotogeneral.hpp"
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
using namespace ui;
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_keeloqtx {
|
||||||
|
|
||||||
|
void KeeloqTXView::focus() {
|
||||||
|
button_open.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::update_hop() {
|
||||||
|
hop = data.btn << 28 | (data.serial & 0x3FF) << 16 | data.counter;
|
||||||
|
|
||||||
|
if (data.mf_name == "Aprimatic") {
|
||||||
|
uint32_t apri_serial = data.serial;
|
||||||
|
uint8_t apr1 = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 1; i != 0b10000000000; i <<= 1) {
|
||||||
|
if (apri_serial & i) apr1++;
|
||||||
|
}
|
||||||
|
|
||||||
|
apri_serial &= 0b00001111111111;
|
||||||
|
|
||||||
|
if (apr1 % 2 == 0) {
|
||||||
|
apri_serial |= 0b110000000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter;
|
||||||
|
} else if (
|
||||||
|
data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449") {
|
||||||
|
hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter;
|
||||||
|
} else if (
|
||||||
|
data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") {
|
||||||
|
hop = data.btn << 28 | (data.serial & 0xFF) << 16 | data.counter;
|
||||||
|
} else if (data.mf_name == "Merlin") {
|
||||||
|
hop = data.btn << 28 | (0x000) << 16 | data.counter;
|
||||||
|
} else if (data.mf_name == "Centurion") {
|
||||||
|
hop = data.btn << 28 | (0x1CE) << 16 | data.counter;
|
||||||
|
} else if (data.mf_name == "Monarch") {
|
||||||
|
hop = data.btn << 28 | (0x100) << 16 | data.counter;
|
||||||
|
} else if (data.mf_name == "Dea_Mio") {
|
||||||
|
uint8_t first_disc_num = (data.serial >> 8) & 0xF;
|
||||||
|
uint8_t result_disc = (0xC + (first_disc_num % 4));
|
||||||
|
|
||||||
|
uint32_t dea_serial = (data.serial & 0xFF) | (((uint32_t)result_disc) << 8);
|
||||||
|
|
||||||
|
hop = data.btn << 28 | (dea_serial & 0xFFF) << 16 | data.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
text_hop.set(to_string_hex(hop));
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::update_payload() {
|
||||||
|
uint64_t encrypt = 0;
|
||||||
|
|
||||||
|
switch (current_key.type) {
|
||||||
|
case KEELOQ_SIMPLE_LEARNING: {
|
||||||
|
encrypt = keeloq_encrypt(hop, current_key.key);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KEELOQ_NORMAL_LEARNING: {
|
||||||
|
uint64_t man = keeloq_normal_learning(fix, current_key.key);
|
||||||
|
|
||||||
|
encrypt = keeloq_encrypt(hop, man);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = (uint64_t)fix << 32 | encrypt;
|
||||||
|
|
||||||
|
uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64);
|
||||||
|
|
||||||
|
text_payload.set(to_string_hex(preview_payload));
|
||||||
|
encode_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::encode_data() {
|
||||||
|
std::string fragments{};
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < 64; ++i) {
|
||||||
|
fragments += keeloq_fragments[bit(payload, i)];
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded_data = KEELOQ_HEADER + fragments + "1001";
|
||||||
|
|
||||||
|
if (data.mf_name == "Sommer") {
|
||||||
|
pause_duration = 28;
|
||||||
|
} else {
|
||||||
|
pause_duration = 39;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeeloqTXView::KeeloqTXView(NavigationView& nav)
|
||||||
|
: nav_{nav} {
|
||||||
|
baseband::run_image(portapack::spi_flash::image_tag_ook);
|
||||||
|
|
||||||
|
add_children({&labels,
|
||||||
|
&text_mf_name,
|
||||||
|
&text_serial,
|
||||||
|
&text_fix,
|
||||||
|
&text_hop,
|
||||||
|
&field_counter,
|
||||||
|
&field_button,
|
||||||
|
&field_repeat,
|
||||||
|
&text_payload,
|
||||||
|
&button_open,
|
||||||
|
&text_status,
|
||||||
|
&progressbar,
|
||||||
|
&tx_view});
|
||||||
|
|
||||||
|
field_counter.on_change = [this](uint32_t value) {
|
||||||
|
data.counter = (uint16_t)value;
|
||||||
|
|
||||||
|
update_hop();
|
||||||
|
update_payload();
|
||||||
|
};
|
||||||
|
|
||||||
|
field_button.on_change = [this](uint32_t value) {
|
||||||
|
data.btn = (uint8_t)value;
|
||||||
|
|
||||||
|
fix &= 0xFFFFFFF;
|
||||||
|
fix |= data.btn << 28;
|
||||||
|
|
||||||
|
text_fix.set(to_string_hex(fix));
|
||||||
|
|
||||||
|
update_hop();
|
||||||
|
update_payload();
|
||||||
|
};
|
||||||
|
|
||||||
|
field_repeat.on_change = [this](uint32_t value) {
|
||||||
|
repeat = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
field_repeat.set_value(4, true);
|
||||||
|
|
||||||
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
|
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||||
|
new_view->on_changed = [this](rf::Frequency f) {
|
||||||
|
transmitter_model.set_target_frequency(f);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_view.on_start = [this]() {
|
||||||
|
start_tx();
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_view.on_stop = [this]() {
|
||||||
|
baseband::kill_ook();
|
||||||
|
stop_tx();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_open.on_select = [this](const Button&) {
|
||||||
|
auto open_view = nav_.push<FileLoadView>(".KEELOQ");
|
||||||
|
|
||||||
|
ensure_directory(keeloq_remotes_dir);
|
||||||
|
open_view->push_dir(keeloq_remotes_dir);
|
||||||
|
open_view->on_changed = [this](fs::path path) {
|
||||||
|
nav_.set_on_pop([this, path]() {
|
||||||
|
if (!read_keeloq_file(path, data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_path = path;
|
||||||
|
|
||||||
|
for (const auto& key : keystore.get_keys()) {
|
||||||
|
if (key.mf_name == data.mf_name) {
|
||||||
|
current_key = key;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fix = data.btn << 28 | data.serial;
|
||||||
|
|
||||||
|
update_hop();
|
||||||
|
|
||||||
|
text_mf_name.set(data.mf_name);
|
||||||
|
text_serial.set(to_string_hex(data.serial));
|
||||||
|
text_fix.set(to_string_hex(fix));
|
||||||
|
|
||||||
|
field_counter.set_value(data.counter, false);
|
||||||
|
field_button.set_value(data.btn, false);
|
||||||
|
|
||||||
|
update_payload();
|
||||||
|
|
||||||
|
field_counter.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::start_tx() {
|
||||||
|
size_t bitstream_length = encoders::make_bitstream(encoded_data);
|
||||||
|
|
||||||
|
progressbar.set_max(repeat);
|
||||||
|
tx_view.set_transmitting(true);
|
||||||
|
|
||||||
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
baseband::set_ook_data(
|
||||||
|
bitstream_length,
|
||||||
|
OOK_SAMPLERATE * (400.0 / 1000000.0),
|
||||||
|
repeat,
|
||||||
|
pause_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::stop_tx() {
|
||||||
|
transmitter_model.disable();
|
||||||
|
text_status.set("Ready");
|
||||||
|
progressbar.set_value(0);
|
||||||
|
tx_view.set_transmitting(false);
|
||||||
|
|
||||||
|
data.counter += 1;
|
||||||
|
|
||||||
|
field_counter.set_value(data.counter, false);
|
||||||
|
|
||||||
|
update_hop();
|
||||||
|
update_payload();
|
||||||
|
|
||||||
|
write_keeloq_file(file_path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeeloqTXView::on_tx_progress(uint32_t progress, bool done) {
|
||||||
|
if (!done) {
|
||||||
|
text_status.set(to_string_dec_uint(progress + 1) + "/" + to_string_dec_uint(repeat));
|
||||||
|
progressbar.set_value(progress + 1);
|
||||||
|
} else {
|
||||||
|
chThdSleepMilliseconds(10);
|
||||||
|
|
||||||
|
stop_tx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeeloqTXView::~KeeloqTXView() {
|
||||||
|
transmitter_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui::external_app::ui_keeloqtx
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#ifndef __KEELOQTX__
|
||||||
|
#define __KEELOQTX__
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_transmitter.hpp"
|
||||||
|
#include "transmitter_model.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
#include "app_settings.hpp"
|
||||||
|
#include "radio_state.hpp"
|
||||||
|
#include "encoders.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "../../keeloq_file.hpp"
|
||||||
|
#include "../../keeloq_keystore.hpp"
|
||||||
|
|
||||||
|
#define KEELOQ_HEADER "101010101010101010101010000000000"
|
||||||
|
|
||||||
|
namespace ui::external_app::ui_keeloqtx {
|
||||||
|
|
||||||
|
class KeeloqTXView : public View {
|
||||||
|
public:
|
||||||
|
KeeloqTXView(NavigationView& nav);
|
||||||
|
~KeeloqTXView();
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
|
|
||||||
|
std::string title() const override {
|
||||||
|
return "KeeLoqTX";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string keeloq_fragments[2] = {"110", "100"};
|
||||||
|
|
||||||
|
NavigationView& nav_;
|
||||||
|
|
||||||
|
TxRadioState radio_state_{
|
||||||
|
433920000,
|
||||||
|
1750000,
|
||||||
|
OOK_SAMPLERATE};
|
||||||
|
|
||||||
|
app_settings::SettingsManager settings_{
|
||||||
|
"tx_keeloq", app_settings::Mode::TX};
|
||||||
|
|
||||||
|
std::filesystem::path file_path{};
|
||||||
|
|
||||||
|
KeeloqKeystore keystore{};
|
||||||
|
KeeloqKey current_key{};
|
||||||
|
KeeloqData data{};
|
||||||
|
|
||||||
|
uint32_t fix = 0;
|
||||||
|
uint32_t hop = 0;
|
||||||
|
uint64_t payload = 0;
|
||||||
|
|
||||||
|
void update_hop();
|
||||||
|
|
||||||
|
Labels labels{
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(0)}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(1)}, "Serial:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(2)}, "Fix:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(3)}, "Hop:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(4)}, "Counter:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(5)}, "Button:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(1), UI_POS_Y(6)}, "Repeat:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{UI_POS_X(2), UI_POS_Y(8)}, "Payload:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
};
|
||||||
|
|
||||||
|
Text text_mf_name{{UI_POS_X(15), UI_POS_Y(0), 128, 16}, ""};
|
||||||
|
Text text_serial{{UI_POS_X(9), UI_POS_Y(1), 64, 16}, "00000000"};
|
||||||
|
Text text_fix{{UI_POS_X(6), UI_POS_Y(2), 64, 16}, "00000000"};
|
||||||
|
Text text_hop{{UI_POS_X(6), UI_POS_Y(3), 64, 16}, "00000000"};
|
||||||
|
|
||||||
|
NumberField field_counter{
|
||||||
|
{UI_POS_X(10), UI_POS_Y(4)},
|
||||||
|
5,
|
||||||
|
{0, 65535},
|
||||||
|
1,
|
||||||
|
' '};
|
||||||
|
NumberField field_button{
|
||||||
|
{UI_POS_X(9), UI_POS_Y(5)},
|
||||||
|
2,
|
||||||
|
{0, 15},
|
||||||
|
1,
|
||||||
|
' '};
|
||||||
|
NumberField field_repeat{
|
||||||
|
{UI_POS_X(10), UI_POS_Y(6)},
|
||||||
|
3,
|
||||||
|
{0, 100},
|
||||||
|
1,
|
||||||
|
' '};
|
||||||
|
|
||||||
|
Text text_payload{{UI_POS_X(2), UI_POS_Y(9), 128, 16}, "0000000000000000"};
|
||||||
|
|
||||||
|
void update_payload();
|
||||||
|
|
||||||
|
Button button_open{{UI_POS_X(0), UI_POS_Y(11), screen_width, 32}, "Open file"};
|
||||||
|
|
||||||
|
Text text_status{{UI_POS_X(2), UI_POS_Y_BOTTOM(7), 128, 16}, "Ready"};
|
||||||
|
|
||||||
|
ProgressBar progressbar{
|
||||||
|
{UI_POS_X(2), UI_POS_Y_BOTTOM(7) + 20, UI_POS_WIDTH_REMAINING(4), 16}};
|
||||||
|
|
||||||
|
TransmitterView tx_view{
|
||||||
|
(int16_t)UI_POS_Y_BOTTOM(4),
|
||||||
|
50000,
|
||||||
|
9};
|
||||||
|
|
||||||
|
std::string encoded_data{};
|
||||||
|
|
||||||
|
void encode_data();
|
||||||
|
|
||||||
|
uint32_t repeat = 4;
|
||||||
|
uint32_t pause_duration = 0;
|
||||||
|
|
||||||
|
void start_tx();
|
||||||
|
void stop_tx();
|
||||||
|
|
||||||
|
void update_progress();
|
||||||
|
void on_tx_progress(uint32_t progress, bool done);
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_tx_progress{
|
||||||
|
Message::ID::TXProgress,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||||
|
this->on_tx_progress(message.progress, message.done);
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui::external_app::ui_keeloqtx
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "keeloq_common.hpp"
|
||||||
|
|
||||||
|
uint32_t keeloq_encrypt(const uint32_t data, const uint64_t key) {
|
||||||
|
uint32_t x = data, r;
|
||||||
|
|
||||||
|
for (r = 0; r < 528; r++)
|
||||||
|
x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^
|
||||||
|
bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31)))
|
||||||
|
<< 31);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t keeloq_decrypt(const uint32_t data, const uint64_t key) {
|
||||||
|
uint32_t x = data, r;
|
||||||
|
|
||||||
|
for (r = 0; r < 528; r++)
|
||||||
|
x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^
|
||||||
|
bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key) {
|
||||||
|
uint32_t k1, k2;
|
||||||
|
|
||||||
|
data &= 0x0FFFFFFF;
|
||||||
|
data |= 0x20000000;
|
||||||
|
k1 = keeloq_decrypt(data, key);
|
||||||
|
|
||||||
|
data &= 0x0FFFFFFF;
|
||||||
|
data |= 0x60000000;
|
||||||
|
k2 = keeloq_decrypt(data, key);
|
||||||
|
|
||||||
|
return ((uint64_t)k2 << 32) | k1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef __KEELOQ_COMMON__
|
||||||
|
#define __KEELOQ_COMMON__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#define bit(x, n) (((x) >> (n)) & 1)
|
||||||
|
#define g5(x, a, b, c, d, e) \
|
||||||
|
(bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16)
|
||||||
|
|
||||||
|
#define KEELOQ_NLF 0x3A5C742E
|
||||||
|
|
||||||
|
uint32_t keeloq_encrypt(const uint32_t data, const uint64_t key);
|
||||||
|
uint32_t keeloq_decrypt(const uint32_t data, const uint64_t key);
|
||||||
|
uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -43,7 +43,7 @@ bool read_keeloq_file(const fs::path& path, KeeloqData& data) {
|
|||||||
|
|
||||||
data.mf_name = std::string{chunks[0]};
|
data.mf_name = std::string{chunks[0]};
|
||||||
data.serial = std::strtoul(chunks[1].data(), NULL, 16);
|
data.serial = std::strtoul(chunks[1].data(), NULL, 16);
|
||||||
data.counter = std::atoi(chunks[2].data());
|
data.counter = (uint16_t)std::atoi(chunks[2].data());
|
||||||
data.btn = (uint8_t)std::atoi(chunks[3].data());
|
data.btn = (uint8_t)std::atoi(chunks[3].data());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -60,7 +60,10 @@ bool write_keeloq_file(const fs::path& path, const KeeloqData& data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string formatted = data.mf_name + ";" + to_string_hex(data.serial) + ";" + to_string_dec_uint(data.counter) + ";" + to_string_dec_uint(data.btn);
|
std::string formatted = data.mf_name + ";" +
|
||||||
|
to_string_hex(data.serial) + ";" +
|
||||||
|
to_string_dec_uint(data.counter) + ";" +
|
||||||
|
to_string_dec_uint(data.btn);
|
||||||
|
|
||||||
file.write_line(formatted);
|
file.write_line(formatted);
|
||||||
file.close();
|
file.close();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
struct KeeloqData {
|
struct KeeloqData {
|
||||||
std::string mf_name{};
|
std::string mf_name{};
|
||||||
uint32_t serial = 0;
|
uint32_t serial = 0;
|
||||||
uint32_t counter = 0;
|
uint16_t counter = 0;
|
||||||
uint8_t btn = 0;
|
uint8_t btn = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Stilmatic;0123456789ABCDEF;2
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Keystore File Specification
|
||||||
|
## Introduction
|
||||||
|
This directory contains the KeeLoq keystore file needed by SubGhzD to decrypt KeeLoq signals and used by the KeeLoq TX app to emulate KeeLoq transmitters.
|
||||||
|
## Where to get more KeeLoq keys
|
||||||
|
To gather more KeeLoq keys you can either extract them directly from the receiver's firmware or you can grab them from Flipper Zero firmwares, such as Unleashed and Momentum.
|
||||||
|
|
||||||
|
To do the latter a Flipper Zero device is required, since the `keeloq_mfcodes` file on the flipper is encrypted using the factory AES-128 keys stored in the its security enclave chip. In order to decrypt them and have them exported for use in Mayhem, you can use [Rocket God's SubGHz toolkit app](https://github.com/RocketGod-git/RocketGods-SubGHz-Toolkit).
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
Each line in the MFCODES file contains three fields, each separated by a semicolon: Manufacturer, key and key type. For example:
|
||||||
|
```
|
||||||
|
Stilmatic;0123456789ABCDEF;2
|
||||||
|
```
|
||||||
|
The type field can be either 1 or 2 currently (KeeLoq simple learning and normal learning, respectively) since those are the currently supported learning modes.
|
||||||
Reference in New Issue
Block a user